How to render MPV to GTK4 window on X11 with python? - python

How can mpv render to GTK4 window or widget?
In GTK3 it was quite easy to get XID (and put it as argument to MPV) but in GTK4 It seem have to be done with GtkX11 and X11Surface https://docs.gtk.org/gdk4-x11/method.X11Surface.get_xid.html
But I have not clue how to do that in python - can't get surface from window/widget.
#!/usr/bin/env python3
import gi
import mpv
gi.require_version('Gtk', '4.0')
gi.require_version('Gdk', '4.0')
gi.require_version('GdkX11', '4.0')
from gi.repository import Gtk, Gdk, GdkX11
class MainClass(Gtk.ApplicationWindow):
def __init__(self, app):
super(MainClass, self).__init__()
self.set_application(app)
self.set_default_size(600, 400)
self.connect("destroy", self.on_destroy)
widget = Gtk.Frame()
self.set_child(widget)
self.present()
# Can't get XID from widget there
self.mpv = mpv.MPV(wid=str(GdkX11.X11Surface.get_xid(widget)))
self.mpv.play("test.webm")
def on_destroy(self, widget, data=None):
self.mpv.terminate()
Gtk.main_quit()
def on_activate(app):
application = MainClass(app)
if __name__ == '__main__':
# This is necessary since like Qt, Gtk stomps over the locale settings needed by libmpv.
# Like with Qt, this needs to happen after importing Gtk but before creating the first mpv.MPV instance.
import locale
locale.setlocale(locale.LC_NUMERIC, 'C')
app = Gtk.Application()
app.connect('activate', on_activate)
app.run(None)
TypeError: argument self: Expected GdkX11.X11Surface, but got gi.repository.Gtk.Frame

I don't know if this is completely correct
GdkX11.X11Surface.get_xid() takes a X11Surface as a parameter
Try this:
self.mpv = mpv.MPV(wid=str(GdkX11.X11Surface.get_xid(self.get_surface())))
This worked for me

Related

How to play custom GUI sounds in GTK3 Python app?

Tinkering with a small GTK3 app in Python3, I can trigger XDG theme sounds like so:
#!/usr/bin/python3
import gi
gi.require_version("Gtk", "3.0")
gi.require_version('GSound', '1.0')
from gi.repository import Gtk, GSound
class PyApp:
def __init__(self):
super(PyApp, self).__init__()
self.builder = Gtk.Builder()
self.builder.add_from_file("test.glade")
handlers = {
"onButtonClick": self.onButtonClick
}
self.builder.connect_signals(handlers)
self.sound = GSound.Context()
self.sound.init()
self.window = self.builder.get_object("window1")
self.window.show_all()
def onButtonClick(self, button):
self.sound.play_simple({ GSound.ATTR_EVENT_ID : "phone-incoming-call" })
if __name__ == "__main__":
app = PyApp()
Gtk.main()
But how do I make GSound play my own sounds? Do I have to construct a custom XDG theme, and if so do I have to install this in the correct system folder (e.g. /usr/share/sounds/), or can I bundle it stand-alone with my app? Also, how do I tell the Gsound.context() which theme to use? Very little information available about this.
Edit: This looked promising:
self.sound = GSound.Context()
self.sound.set_attributes({GSound.ATTR_CANBERRA_XDG_THEME_NAME: "freedesktop"})
Alas:
gi.repository.GLib.Error: gsound - error - quark: Invalid argument (-2)

Is it possible to add an Animation with AppIndicator3?

I'm trying to create a simple AppIndicator with Python.
The code is pretty similar with:
import gi
from gi.repository import Gtk
from gi.repository import AppIndicator3 as appindicator
APPINDICATOR_ID = 'myapp'
def main():
indicator = appindicator.Indicator.new(APPINDICATOR_ID, '/usr/share/myapp/images/icon.svg', appindicator.IndicatorCategory.SYSTEM_SERVICES)
indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
Gtk.main()
It works very well, but I would like to introduce an animated icon. A blinking icon for example.
Not a static one.
I tried to convert the SVG file to GIF, but it does not work as well.
Is there a way to create a GdkPixbuf.PixbufAnimation (example) within AppIndicator3?
If yes, How can I do it?
I don't know if my answer will work for you but I see you are only importing libraries, assigning a variable, and defining some code. You may want to have the main(): code loop by typing:
This will give you the option to add return True or return False:
import gi
from gi.repository import Gtk
from gi.repository import AppIndicator3 as appindicator
APPINDICATOR_ID = 'myapp'
def main():
indicator = appindicator.Indicator.new(APPINDICATOR_ID, '/usr/share/myapp/images/icon.svg', appindicator.IndicatorCategory.SYSTEM_SERVICES)
indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
Gtk.main()
while main():
pass
Or if you want the code to keep going until you click the stop button:
import gi
from gi.repository import Gtk
from gi.repository import AppIndicator3 as appindicator
APPINDICATOR_ID = 'myapp'
def main():
indicator = appindicator.Indicator.new(APPINDICATOR_ID, '/usr/share/myapp/images/icon.svg', appindicator.IndicatorCategory.SYSTEM_SERVICES)
indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
Gtk.main()
main()
I found a workaround for this. Specially, if you are trying to configure a GIF.
For me, create a manual GIF worked. This solution requires a better approach related to concurrency but the prototype is here:
import gi
import time
import signal
import threading
gi.require_version('Gtk', '3.0')
gi.require_version('AppIndicator3', '0.1')
from gi.repository import Gtk, GLib
from gi.repository import AppIndicator3 as appindicator
APPINDICATOR_ID = 'myapp'
finished = False
def change_icon(indicator, index=5):
indicator.set_icon("spinner-%s.svg" % str(index))
def render_icon(indicator):
i = 0
while True:
# I have 10 SVG images
index = (i % 10)
GLib.idle_add(change_icon, indicator, index)
time.sleep(0.5)
if finished:
break
i += 1
def on_click(widget):
global finished
finished = True
Gtk.main_quit()
def main():
indicator = appindicator.Indicator.new(APPINDICATOR_ID, 'spinner-0.svg', appindicator.IndicatorCategory.SYSTEM_SERVICES)
indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
menu = Gtk.Menu()
quit = Gtk.MenuItem('Quit')
quit.connect('activate', on_click)
menu.append(quit)
menu.show_all()
indicator.set_menu(menu)
thread = threading.Thread(target=render_icon, args=(indicator,))
thread.daemon = True
thread.start()
signal.signal(signal.SIGINT, signal.SIG_DFL)
Gtk.main()
main()
I created a spinner with 10 different frames and I can iterate using a Thread and set_icon + GLib.iddle_add().
Again, it requires a better solution in terms of Thread concorrency, but it works as a starting point.

Why I receive this error about attribute in Python?

I have a problem in a simple Glade/Gtk project, I have some objects like Button, Text inputs etc. I have a basic error, gi.repository.Gtk has no attribute 'builder'. Any Ideas? Thanks in advance
Here's the error that I receive (https://imgur.com/a/14cEdn0)
import gi
gi.require_version('Gtk', '3.0')
import gi.repository
from gi.repository import Gtk
class Main:
def __init__(self):
gladeFile = Gtk.builder
self.builder = Gtk.Builder()
self.builder.add_from_file(gladeFile)
self.builder.connect_signals(self)
window = self.builder.get_object("button1")
window.show()
if __name__ == '__main__':
main = Main()
Gtk.main()
Looks like your gladeFile should be a string containing the path to your glade file, e.g. gladeFile = "example.glade".

python-vlc will not embed gtk widget into window, but open a new window instead

I'm working on a gtk3 front end for libvlc written in python using python-vlc. I'm following the gtk3 example from the python-vlc github page, but am experiencing strange behavior. I have a widget that looks like that:
import gi
import sys
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class PlayerWidget(Gtk.DrawingArea):
__gtype_name__ = 'VLCWidget'
def __init__(self, instance):
Gtk.DrawingArea.__init__(self)
self.player = instance.media_player_new()
def handle_embed(*args):
if sys.platform == 'win32':
self.player.set_hwnd(self.get_window().get_handle())
else:
self.player.set_xwindow(self.get_window().get_xid())
return True
self.connect("realize", handle_embed)
self.set_size_request(320, 200)
I embed it here:
import vlc
import sys
from widgets.player import PlayerWidget
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class VideoPlayer(Gtk.Window):
CONST_APP_NAME = "video player"
def __init__(self):
Gtk.Window.__init__(self)
if 'linux' in sys.platform:
self.instance = vlc.Instance("--no-xlib")
else:
self.instance = vlc.Instance()
self.set_default_size(800, 600)
header = Gtk.HeaderBar(title=self.CONST_APP_NAME)
header.set_subtitle("Filename.mp4")
header.set_show_close_button(True) # this one is the troublemaker
self.set_titlebar(header)
self.connect("destroy", Gtk.main_quit)
self.player_widget = PlayerWidget(self.instance)
self.add(self.player_widget)
def show_window(self):
self.show_all()
Gtk.main()
def set_media(self, fname):
self.player_widget.player.set_media(self.instance.media_new(fname))
def play(self):
self.player_widget.play()
if not len(sys.argv) > 0:
print('Please provide a filename')
sys.exit(1)
p = VideoPlayer()
p.set_media(sys.argv[1])
p.play()
p.show_window()
p.instance.release()
It works fine if I embed it into an empty Gtk.window. If, however, I add a HeaderBar to that window as well and then add a close button to that HeaderBar using set_show_close_button(True) it stops working as expected. The PlayerWidget will not be shown embedded anymore, but instead a new (second) window will be opened where the video is played. If I do not add the close button to the HeaderBar the widget gets embedded just fine.
A warning is thrown to the console: xcb_window window error: X server failure
I first thought it could be because I use gnome under wayland, but it occurs on X as well as on wayland.
Any help is appreciated.
Update 1: Added full code example. When I ran it today, the first time it actually worked as expected, but after that the same bug as described above occured again. Very weird.
As #mtz and #stovfl correctly pointed out, the problem was that I started the video playback (p.play()) before creating the window (p.show_window()).
As suggested I used GLib.idle_add(p.play) to let the window start the playback once it's ready. The GLib module can be imported using from gi.repository import GLib.

Python Gtk.MessageDialog Hides Parent Window

I am working on a Gtk3 app written in Python. The main window for my app is set up as follows:
#!/bin/python
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk as Gtk
## OTHER IMPORTS
class MainGui(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="APP TITLE")
# Defaults
self.set_default_size(600, 500)
## OTHER CODE
# Setup the Window
self.connect("destroy", self.on_close)
self.show_all()
## OTHER CODE
def on_close(self, widget):
if self.editor.get_document().get_has_changes():
save_dialog = Gtk.MessageDialog(self, 0,
Gtk.MessageType.QUESTION,
Gtk.ButtonsType.YES_NO,
"Save changes?")
response = save_dialog.run()
## REST OF DIALOG HANDELING
The problem I'm having is related to the save dialog. The app displays the dialog just fine, but it hides my main window, which is not the desired effect. I've tried searching around for a solution, but can't seem to figure out what I'm doing wrong. Any help would be greatly appreciated!
Shortly after posting this I realized that the reason things weren't working is because of a bone-headed mistake. I was hooking up my on_close method using this:
self.connect("destroy", self.on_close)
It turns out I should be doing it this way:
self.connect("delete-event", self.on_close)
Now things work great.

Categories

Resources