Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
cd16c49
Replacing QRegExp with QRegularExpression for Qt6 compatibility in mi…
jonoomph Dec 18, 2025
f91b09f
Initial integration of qt_api shim, to switch between different versi…
jonoomph Dec 18, 2025
92a0e35
Initial PyQt6 support in our qt_api shim - tested in Ubuntu 25.04 VM.…
jonoomph Dec 19, 2025
6e0a415
Initial support for PySide6, tested on Ubuntu 25.04 VM.
jonoomph Dec 19, 2025
7d76d4a
Removing mentions of pyside2 (never supported)
jonoomph Dec 19, 2025
94c40ab
Updating develoers docs with PyQt5, PyQt6, and PySide6 packages and i…
jonoomph Dec 19, 2025
866b996
Merge branch 'develop' into qt6-support
jonoomph Feb 2, 2026
1fd295e
Fixing merge issue, after merging develop back into this branch
jonoomph Feb 2, 2026
ece2c80
Fixing many new PyQt5/PyQt6 issues after merging in develop branch (t…
jonoomph Feb 2, 2026
785d46e
Fixing a few missing PyQt6 imports in qt_api
jonoomph Feb 2, 2026
57b7ddc
Fixing some regex issues with PyQt6, preventing filtering from workin…
jonoomph Feb 2, 2026
5c26290
Fixing a few minor PyQt6 errors and attempting to fix a minor focus/t…
jonoomph Feb 2, 2026
2df764e
Fixing PyQt5 hard-coded import in focus tabstops code, which breaks P…
jonoomph Feb 2, 2026
46b273f
Replacing more hard-coded PyQt5 imports with qt_api shims.
jonoomph Feb 2, 2026
39d76b3
Replaced all explicit Qt base-class __init__ calls with super().__ini…
jonoomph Feb 2, 2026
60cebe8
Fixing a Preferences error for PySide6 - causing the window to fail t…
jonoomph Feb 2, 2026
9b9ec29
Fixing title bar height and margins for PySide6 compatibility (and fo…
jonoomph Feb 2, 2026
af87185
Fixing tutorial dialog for better support for PyQt6 & PySide6 (import…
jonoomph Feb 2, 2026
e304c85
Another fix to Tutorial dialog to use associatedWidgets for PyQt6 & P…
jonoomph Feb 2, 2026
04ac0ee
Improving Tutorial dialog to be compatible with PyQt6/PySide6
jonoomph Feb 3, 2026
2c0340f
Fixing regression in PyQt6 tutorial
jonoomph Feb 3, 2026
ae4c3de
Removing selection rectangles from Transitions, Effects, and Emojis (…
jonoomph Feb 3, 2026
f4406d6
Protect against setting audio sample rate to None in CheckAudioDevice…
jonoomph Feb 3, 2026
0c10dc5
Adding Android-only openshot_shiboken_ext handling for wrapInstance, …
jonoomph Feb 3, 2026
b3d7e6a
Protecting setTabOrder calls from potentially using widgets from diff…
jonoomph Feb 3, 2026
18a95f0
Moving to exec() method instead of exec_() when launching OpenShot (i…
jonoomph Feb 3, 2026
9b60438
Quite theme miss logs (too noisy at the moment)
jonoomph Feb 3, 2026
2ade796
Reduce grid size of Emoji list view (they have too much horizontal pa…
jonoomph Feb 3, 2026
9829d07
Silence more theme misses (when applying theme colors, icons, and sty…
jonoomph Feb 3, 2026
21232e3
Merge branch 'develop' into qt6-support
jonoomph Feb 14, 2026
8daf0b7
Fixing merge regression with QRegEx import for Qt6 path
jonoomph Feb 14, 2026
52705ac
Fixing more regressions from merging develop into this branch
jonoomph Feb 14, 2026
94c74d5
Another Qt6 regression with emoji dragging
jonoomph Feb 14, 2026
113dbf1
Another Qt6 regression with file adding and dragging
jonoomph Feb 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions doc/developers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ unofficial Ubuntu repository, which has our software packages available to downl
sudo add-apt-repository ppa:openshot.developers/libopenshot-daily
sudo apt-get update
sudo apt-get install openshot-qt \
build-essential \
cmake \
catch2 \
libx11-dev \
libasound2-dev \
libavcodec-dev \
Expand All @@ -99,13 +101,45 @@ unofficial Ubuntu repository, which has our software packages available to downl
libzmq3-dev \
pkg-config \
python3-dev \
python3-pyqt5.qtwebengine \
python3-zmq \
protobuf-compiler \
qtbase5-dev \
libqt5svg5-dev \
libxcb-xfixes0-dev \
qtmultimedia5-dev \
swig

OpenShot supports PyQt5, PyQt6, and PySide6. libopenshot must be built
against the correct Qt version for the binding you are using
(Qt5: PyQt5, Qt6: PyQt6 or PySide6).

For PySide6 (Qt6) bindings, install these packages:

.. code-block:: bash

sudo apt-get install \
python3-pyside6.qtcore \
python3-pyside6.qtgui \
python3-pyside6.qtwidgets \
python3-pyside6.qtstatemachine \
python3-pyside6.qtsvg \
python3-pyside6.qtwebenginecore \
python3-pyside6.qtwebenginewidgets \
python3-pyside6.qtwebchannel \
python3-pyside6.qtuitools \
shiboken6

For PyQt6, install these packages:

.. code-block:: bash

sudo apt-get install \
qt6-base-dev \
qt6-base-dev-tools \
qt6-tools-dev \
qt6-svg-dev

At this point, you should have all 3 OpenShot components source code cloned into local folders, the OpenShot
daily PPA installed, and all of the required development and runtime dependencies installed. This is a
great start, and we are now ready to start compiling some code!
Expand Down
20 changes: 15 additions & 5 deletions src/classes/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
import traceback
import json

from PyQt5.QtCore import PYQT_VERSION_STR, QT_VERSION_STR, pyqtSlot
from PyQt5.QtWidgets import QApplication, QMessageBox
from qt_api import QT_API, QT_VERSION_STR, BINDING_VERSION_STR, Slot
from qt_api import QApplication, QMessageBox

# Disable sandbox support for QtWebEngine (required on some Linux distros
# for the QtWebEngineWidgets to be rendered, otherwise no timeline is visible).
Expand Down Expand Up @@ -121,6 +121,12 @@ def __init__(self, *args, **kwargs):

# Log some basic system info
self.log = log
# Clear any stale override cursor (can suppress widget cursors in PyQt5)
try:
while QApplication.overrideCursor():
QApplication.restoreOverrideCursor()
except Exception:
pass
self.show_environment(info, openshot)
if self.mode != "unittest":
self.check_libopenshot_version(info, openshot)
Expand Down Expand Up @@ -169,8 +175,7 @@ def show_environment(self, info, openshot):
log.info("processor: %s" % platform.processor())
log.info("machine: %s" % platform.machine())
log.info("python version: %s" % platform.python_version())
log.info("qt5 version: %s" % QT_VERSION_STR)
log.info("pyqt5 version: %s" % PYQT_VERSION_STR)
log.info("qt binding: %s (Qt %s, binding %s)" % (QT_API, QT_VERSION_STR, BINDING_VERSION_STR))

# Look for frozen version info
version_path = os.path.join(info.PATH, "settings", "version.json")
Expand Down Expand Up @@ -335,10 +340,15 @@ def show_errors(self):
def _tr(self, message):
return self.translate("", message)

@pyqtSlot()
@Slot()
def cleanup(self):
"""aboutToQuit signal handler for application exit"""
self.log.debug("Saving settings in app.cleanup")
if getattr(self, "window", None):
try:
self.window._shutdown()
except Exception:
self.log.warning("Window shutdown raised during app cleanup.", exc_info=1)
try:
self.settings.save()
except Exception:
Expand Down
2 changes: 1 addition & 1 deletion src/classes/clipboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

import pickle
import json
from PyQt5.QtCore import QMimeData
from qt_api import QMimeData
from classes.query import QueryObject


Expand Down
2 changes: 1 addition & 1 deletion src/classes/exporters/edl.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import os
from operator import itemgetter

from PyQt5.QtWidgets import QFileDialog
from qt_api import QFileDialog

from classes import info
from classes.app import get_app
Expand Down
2 changes: 1 addition & 1 deletion src/classes/exporters/final_cut_pro.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from xml.dom import minidom

import openshot
from PyQt5.QtWidgets import QFileDialog
from qt_api import QFileDialog

from classes import info
from classes.app import get_app
Expand Down
2 changes: 1 addition & 1 deletion src/classes/importers/edl.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from operator import itemgetter

import openshot
from PyQt5.QtWidgets import QFileDialog
from qt_api import QFileDialog

from classes import info
from classes.app import get_app
Expand Down
2 changes: 1 addition & 1 deletion src/classes/importers/final_cut_pro.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from xml.dom import minidom, Node

import openshot
from PyQt5.QtWidgets import QFileDialog
from qt_api import QFileDialog

from classes import info
from classes.app import get_app
Expand Down
16 changes: 10 additions & 6 deletions src/classes/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,21 @@
}

try:
from PyQt5.QtCore import QSize
from qt_api import QSize

# UI Thumbnail settings
LIST_ICON_SIZE = QSize(100, 65)
LIST_GRID_SIZE = LIST_ICON_SIZE + QSize(5, 25)
TREE_ICON_SIZE = QSize(75, 49)
EMOJI_ICON_SIZE = QSize(75, 75)
EMOJI_GRID_SIZE = EMOJI_ICON_SIZE + QSize(5, 25)
# Runtime emoji selections
EMOJI_FILES = {}
EMOJI_PATH = ""
EMOJI_ICON = ""
except ImportError:
# Fail gracefully if we're running without PyQt5 (e.g. CI tasks)
print("Failed to import `PyQt5.QtCore.QSize` (ignoring exception)")
# Fail gracefully if we're running without Qt (e.g. CI tasks)
print("Failed to import `qt_api.QSize` (ignoring exception)")

# Maintainer details, for packaging
JT = {"name": "Jonathan Thomas",
Expand Down Expand Up @@ -143,7 +147,7 @@

# Compile language list from :/locale resource
try:
from PyQt5.QtCore import QDir
from qt_api import QDir
langdir = QDir(language_path)
trpaths = langdir.entryList(
['OpenShot_*.qm'],
Expand All @@ -154,8 +158,8 @@
lang=trpath[trpath.find('_')+1:-3]
SUPPORTED_LANGUAGES.append(lang)
except ImportError:
# Fail gracefully if we're running without PyQt5 (e.g. CI tasks)
print("Failed to import `PyQt5.QtCore.QDir` (ignoring exception)")
# Fail gracefully if we're running without Qt (e.g. CI tasks)
print("Failed to import `qt_api.QDir` (ignoring exception)")

SETUP = {
"name": NAME,
Expand Down
2 changes: 1 addition & 1 deletion src/classes/language.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import os
import locale

from PyQt5.QtCore import QLocale, QLibraryInfo, QTranslator, QCoreApplication
from qt_api import QLocale, QLibraryInfo, QTranslator, QCoreApplication

from classes.logger import log
from classes import info
Expand Down
2 changes: 1 addition & 1 deletion src/classes/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

import openshot

from PyQt5.QtCore import QTimer, QT_VERSION_STR, PYQT_VERSION_STR
from qt_api import QTimer, QT_VERSION_STR, PYQT_VERSION_STR
from functools import partial

try:
Expand Down
2 changes: 1 addition & 1 deletion src/classes/openshot_rc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore
from qt_api import QtCore

qt_resource_data = b"\
\x00\x02\xc8\x54\
Expand Down
2 changes: 1 addition & 1 deletion src/classes/qt_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
"""

from PyQt5.QtCore import QByteArray
from qt_api import QByteArray


# Utility functions for handling qt types
Expand Down
20 changes: 16 additions & 4 deletions src/classes/tabstops.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
@brief Auto-assign tab order based on on-screen widget geometry.
"""

from PyQt5.QtCore import Qt, QPoint, QTimer
from PyQt5.QtWidgets import (
from qt_api import Qt, QPoint, QTimer
from qt_api import (
QWidget,
QLayout,
QToolBar,
Expand Down Expand Up @@ -103,6 +103,18 @@ def _is_focusable(widget, root, include_hidden, include_disabled):
return True


def safe_set_tab_order(first, second):
"""Set tab order only when both widgets share the same window."""
if first is None or second is None:
return
try:
if first.window() is not second.window():
return
except RuntimeError:
return
QWidget.setTabOrder(first, second)


def _position_key(widget, root, fallback_index, row_tolerance):
try:
pos = widget.mapTo(root, QPoint(0, 0))
Expand Down Expand Up @@ -451,7 +463,7 @@ def apply_auto_tab_order(root, include_hidden=False, include_disabled=False, row
pass # Widget may not support dynamic attributes or may be deleted

for first, second in zip(ordered_widgets, ordered_widgets[1:]):
QWidget.setTabOrder(first, second)
safe_set_tab_order(first, second)


def apply_auto_tab_order_later(root, include_hidden=False, include_disabled=False, row_tolerance=8):
Expand Down Expand Up @@ -502,7 +514,7 @@ def apply_explicit_tab_order(
ordered.append(widget)
seen.add(widget)
for first, second in zip(ordered, ordered[1:]):
QWidget.setTabOrder(first, second)
safe_set_tab_order(first, second)


def apply_explicit_tab_order_later(
Expand Down
11 changes: 8 additions & 3 deletions src/classes/title_bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
"""

from PyQt5.QtWidgets import QWidget, QHBoxLayout, QPushButton, QLabel
from qt_api import QWidget, QHBoxLayout, QPushButton, QLabel

from classes.app import get_app

Expand Down Expand Up @@ -73,9 +73,14 @@ def __init__(self, dock_widget, title_text=""):
layout.addWidget(self.undock_button)
layout.addWidget(self.close_button)

# Set margins and reduce height for the title bar
# Set margins and height for the title bar
layout.setContentsMargins(0, 0, 0, 0)
self.setFixedHeight(20) # Reduced height
if title_text:
# Taller for title with bottom margin
self.setFixedHeight(40)
else:
# Shorter for just drag handle + buttons (tabbed docks)
self.setFixedHeight(20)
self._update_accessible_labels()

def update_title(self, text):
Expand Down
44 changes: 28 additions & 16 deletions src/classes/ui_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@
except ImportError:
from xml.etree import ElementTree

from PyQt5.QtCore import Qt, QDir, QLocale
from PyQt5.QtGui import QIcon, QPalette, QColor
from PyQt5.QtWidgets import (
from qt_api import Qt, QDir, QLocale
from qt_api import QIcon, QPalette, QColor
from qt_api import (
QApplication, QWidget, QTabWidget, QAction)
from PyQt5 import uic
from qt_api import uic, load_ui as qt_load_ui

from classes.app import get_app
from classes.logger import log
Expand Down Expand Up @@ -77,7 +77,10 @@ def load_ui(window, path):
for attempt in range(1, 6):
try:
# Load ui from configured path
uic.loadUi(path, window)
if uic is not None and hasattr(uic, "loadUi"):
uic.loadUi(path, window)
else:
qt_load_ui(path, window)

# Successfully loaded UI file, so clear any previously encountered errors
error = None
Expand Down Expand Up @@ -254,11 +257,16 @@ def connect_auto_events(window, elem, name):
func_name = name + "_trigger"
if hasattr(window, func_name) and callable(getattr(window, func_name)):
# Disconnect existing connections safely
try:
while True:
elem.triggered.disconnect()
except TypeError:
pass # No more connections to disconnect
while True:
try:
disconnected = elem.triggered.disconnect()
except TypeError:
break # No more connections to disconnect (PyQt)
except Exception:
break
else:
if disconnected is False:
break # No more connections to disconnect (PySide)
# Connect the signal to the slot
elem.triggered.connect(getattr(window, func_name))

Expand All @@ -267,11 +275,16 @@ def connect_auto_events(window, elem, name):
func_name = name + "_click"
if hasattr(window, func_name) and callable(getattr(window, func_name)):
# Disconnect existing connections safely
try:
while True:
elem.clicked.disconnect()
except TypeError:
pass # No more connections to disconnect
while True:
try:
disconnected = elem.clicked.disconnect()
except TypeError:
break # No more connections to disconnect (PyQt)
except Exception:
break
else:
if disconnected is False:
break # No more connections to disconnect (PySide)
# Connect the signal to the slot
elem.clicked.connect(getattr(window, func_name))

Expand Down Expand Up @@ -314,4 +327,3 @@ def transfer_children(from_widget, to_widget):
log.info(
"Transferring children from '%s' to '%s'",
from_widget.objectName(), to_widget.objectName())

Loading
Loading