utak3r's virtual shelter

Tag: Qt

Pluginy aplikacji Qt5

Tworzenie systemu pluginów aplikacji działającej na różnych systemach wymagało zawsze wielokrotnej implementacji tego samego – dla każdego systemu oddzielnie, głównie z powodu sposobu dynamicznego ładowania bibliotek. W Qt jednak jest inaczej – framework ten udostępnia bardzo sprawnie działający system pluginów.

Na początek zdefiniujmy interfejs (API):

#ifndef DESIGNLAYER_API_H
#define DESIGNLAYER_API_H

#include 
#include 

namespace apophysis
	{		
	class DesignLayerAPI
		{
		public:
			virtual ~DesignLayerAPI() {};
			virtual QString getName() = 0;
		};
	}

#define DesignLayerAPI_iid "org.apophysis.DesignLayerAPI"

Q_DECLARE_INTERFACE(apophysis::DesignLayerAPI, DesignLayerAPI_iid)

#endif // DESIGNLAYER_API_H

Widzimy tu wirtualną klasę, definiującą metodę getName(). Po definicji API, następuje definicja interfejsu, po którym Qt będzie rozpoznawać nasze pluginy, implementujące te API.

Czas na plugin. Deklarujemy klasę implementującą API:

#ifndef DESIGNLAYER_IMAGE_H
#define DESIGNLAYER_IMAGE_H

#include 
#include 
#include 

class designLayerImage : public QObject, apophysis::DesignLayerAPI
	{
	Q_OBJECT
		Q_PLUGIN_METADATA(IID "org.apophysis.DesignLayerAPI" FILE "designLayerImage.json")
		Q_INTERFACES(apophysis::DesignLayerAPI)

	public:
		designLayerImage();
		QString getName() Q_DECL_OVERRIDE;
	private:
		QString theName;
	};

#endif // DESIGNLAYER_IMAGE_H

Oprócz deklaracji metod zadeklarowanych w interfejsie, widzimy tu jeszcze jedną rzecz: definicję metadanych. Po co to? Metadane pluginu można odczytać bez faktycznego ładowania biblioteki. Zapisujemy je w pliku JSON, np:

{
"type"			:	"Layer",
"name"			:	"Image layer plugin",
"shortname"		:	"ImageLayer",
"version"		:	"0.1",
"author"		:	"Apophysis internal",
"description"	:	"Used mainly for backgrounds: solid colour, gradients, images, etc."
}

Właściwa implementacja jest oczywista, żadnej magii tu nie ma:

#include "designLayerImage.h"

designLayerImage::designLayerImage()
	:
	theName(QLatin1Literal("Unnamed image layer"))
	{
	}

QString designLayerImage::getName()
	{
	return theName;
	}

OK, mamy plugin. Jak go użyć? Na szczęście, to również jest proste (i przenośne! Na pewno działa bez zmian pod Windows i Linux, pod OSX nie sprawdzałem).

Najpierw sprawdzimy w działaniu system metadanych. Załóżmy, że pluginy mamy w podkatalogu plugins (niespodzianka!):

#include 
#include 

void listPlugins()
	{
	QDir pluginsDir(qApp->applicationDirPath());
	pluginsDir.cd(QLatin1Literal("plugins"));
	foreach(QString fileName, pluginsDir.entryList(QDir::Files, QDir::Name))
		{
		QString libExt;
#if defined(Q_OS_WIN)
		libExt = QLatin1Literal("dll");
#elif defined(Q_OS_MAC)
		libExt = QLatin1Literal("bundle");
#elif defined(Q_OS_LINUX)
		libExt = QLatin1Literal("so");
#endif
		if (QFileInfo(fileName).suffix().toLower() == libExt)
			{
			QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
			qDebug() << "File:" << fileName;
			QJsonObject jsonMetaData = pluginLoader.metaData().value(QLatin1Literal("MetaData")).toObject();
			qDebug() << jsonMetaData.value(QLatin1Literal("type")).toString();
			qDebug() << jsonMetaData.value(QLatin1Literal("name")).toString();
			qDebug() << jsonMetaData.value(QLatin1Literal("shortname")).toString();
			qDebug() << jsonMetaData.value(QLatin1Literal("version")).toString();
			qDebug() << jsonMetaData.value(QLatin1Literal("author")).toString();
			qDebug() << jsonMetaData.value(QLatin1Literal("description")).toString();
			}
		}
	}

W zasadzie moglibyśmy się obejść bez sekcji sprawdzania rozszerzenia plików, ale używam tego w razie, gdyby w katalogu były jeszcze jakieś inne pliki (ot, chociażby PDB).

OK, czas na crème de la crème, czyli stworzenie instancji klasy zaimplementowanej w pluginie:

void apophysis::Design::addLayer(const QString & aLayerType)
	{
	QDir pluginsDir(qApp->applicationDirPath());
	pluginsDir.cd(QLatin1Literal("plugins"));
	foreach(QString fileName, pluginsDir.entryList(QDir::Files, QDir::Name))
		{
		QString libExt;
#if defined(Q_OS_WIN)
		libExt = QLatin1Literal("dll");
#elif defined(Q_OS_MAC)
		libExt = QLatin1Literal("bundle");
#elif defined(Q_OS_LINUX)
		libExt = QLatin1Literal("so");
#endif
		if (QFileInfo(fileName).suffix().toLower() == libExt)
			{
			QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
			QString plugintype = pluginLoader.metaData().value(QLatin1Literal("MetaData")).toObject().value(QLatin1Literal("type")).toString();
			QString pluginshortname = pluginLoader.metaData().value(QLatin1Literal("MetaData")).toObject().value(QLatin1Literal("shortname")).toString();
			if ((plugintype == QLatin1Literal("Layer")) && (pluginshortname == aLayerType))
				{
				QObject *plugin = pluginLoader.instance();
				if (plugin)
					{
					apophysis::DesignLayerAPI *layer = qobject_cast(plugin);
					if (layer)
						theLayers.append(layer);
					}
				}
			}
		}
	}

Jak widać, zarówno tworzenie, jak i używanie pluginów w tym systemie jest trywialne - i jest całkowicie przenośne.

Happy coding!

Zmiana parametrów ekranu z C++ pod Windows 7

Czasem zachodzi potrzeba zmiany ustawień monitora z poziomu aplikacji. Czasem chodzi nam o rozdzielczość, czasem o odświeżanie. Mi akurat chodziło o odświeżanie – aplikacja wyświetla materiał video w różnych formatach i potrzebowałem dostosować monitor do materiału, aby osiągnąć jak najlepszą synchronizację.

Moja aplikacja jest w Qt (a jakże), więc zaczynam od sprawdzenia, na którym monitorze jest wyświetlona moja aplikacja:

int monitor = QApplication::desktop()->screenNumber(ui.centralWidget);

Aby napisać działającą poprawnie metodę zmieniającą parametry wyświetlania pod Windows 7, musimy podać dokładną nazwę urządzenia. Podawanie NULL, która wg dokumentacji MSDN powinna zadziałać na domyślnym urządzeniu, pod Windows 7 kończy się błędem niepoprawnego parametru. Dlatego najpierw listujemy wszystkie dostępne urządzenia – i jeśli jest to nasze urządzenie, pobieramy jego aktualne ustawienia i modyfikujemy je:

DEVMODE winMode;
DISPLAY_DEVICE displayDevice;
memset(&displayDevice, 0, sizeof(DISPLAY_DEVICE));
displayDevice.cb = sizeof(displayDevice);
DWORD iDevNum = 0;
if (EnumDisplayDevices(NULL, iDevNum, &displayDevice, EDD_GET_DEVICE_INTERFACE_NAME))
{
	if (EnumDisplaySettings(displayDevice.DeviceName, ENUM_CURRENT_SETTINGS, &winMode) != 0)
	{
		winMode.dmPelsWidth = aWidth;
		winMode.dmPelsHeight = aHeight;
		winMode.dmDisplayFrequency = aFreq;
		ChangeDisplaySettingsEx(displayDevice.DeviceName, &winMode, NULL, CDS_NORESET | CDS_UPDATEREGISTRY, NULL);
	}
}

Wg dokumentacji, po użyciu ChangeDisplaySettingsEx powinniśmy jeszcze spowodować rozesłanie komunikatu do wszystkich aplikacji, wywołując ją jeszcze raz w ten sposób:

ChangeDisplaySettings(NULL, 0);

Składając wszystko do kupy, napisałem sobie metodę, która bierze parametry ustawień (rozdzielczość i odświeżanie) i sprawdza, czy podałem wartość większą od zera (gdybym chciał ustawić tylko niektóre z tych parametrów):

//! Change display (monitor) settings. If you don't want to change any of the values, pass 0 for them.
static bool setCurrentDisplayMode(int aWidth, int aHeight, int aFreq, int aDisplay = 0);
bool sysWindowsApi::setCurrentDisplayMode(int aWidth, int aHeight, int aFreq, int aDisplay)
{
	bool ret = false;
	DEVMODE winMode;
	DISPLAY_DEVICE displayDevice;
	memset(&displayDevice, 0, sizeof(DISPLAY_DEVICE));
	displayDevice.cb = sizeof(displayDevice);
	if (EnumDisplayDevices(NULL, aDisplay, &displayDevice, EDD_GET_DEVICE_INTERFACE_NAME))
	{
		if (EnumDisplaySettings(displayDevice.DeviceName, ENUM_CURRENT_SETTINGS, &winMode) != 0)
		{
			if (aWidth > 0) winMode.dmPelsWidth = aWidth;
			if (aHeight > 0) winMode.dmPelsHeight = aHeight;
			if (aFreq > 0) winMode.dmDisplayFrequency = aFreq;
			if (ChangeDisplaySettingsEx(displayDevice.DeviceName, &winMode, NULL, CDS_NORESET | CDS_UPDATEREGISTRY, NULL) == DISP_CHANGE_SUCCESSFUL)
			{
				ChangeDisplaySettings(NULL, 0);
				ret = true;
			}
		}
	}
	return ret;
}

Z głównej aplikacji wywołuję ją np. tak:

sysWindowsApi::setCurrentDisplayMode(0, 0, 60, QApplication::desktop()->screenNumber(ui.centralWidget));

powoduje to zmianę tylko częstotliwości odświeżania, pozostawiając rozdzielczość bez zmian – i tylko na tym monitorze, na którym znajduje się nasza aplikacja.

Qt 5.3.2 vs GCC 4.8.1 TDM64

Kolejna wersja Qt, kolejna kompilacja… tym razem budujemy Qt 5.3.2 pod GCC pod Windows, w wydaniu TDM64.

Najpierw – konfiguracja. Ja stworzyłem sobie plik configure.cmd:

cd Qt-5.3.2-gcc-x86_64

set PATH_GCC=C:\gcc\gcc-4.8.1-tdm64
set PATH_PERL=C:\Perl
set PATH_PYTHON=C:\Python27
set PATH_ICU=D:\devel\Qt\Qt5_deps\icu
set PATH_OPENSSL=D:\devel\Qt\Qt5_deps\openssl-1.0.1e

set INCLUDE=%PATH_ICU%\dist\include;%PATH_OPENSSL%\dist\include
set LIB=%PATH_ICU%\dist\lib;%PATH_OPENSSL%\dist\lib
set QMAKESPEC=
set QTDIR=

set PATH=%CD%\qtbase\bin;%CD%\gnuwin32\bin;%PATH_GCC%\bin;%PATH_PERL%\bin;%PATH_PYTHON%;%PATH_ICU%\dist\lib;%PATH_OPENSSL%\dist\bin;%SystemRoot%\System32
set MAKE_COMMAND=

configure -release -opensource -confirm-license -platform win32-g++ -developer-build -c++11 -opengl desktop -plugin-sql-odbc -qt-style-windowsxp -qt-style-windowsvista -nomake tests -nomake examples -skip qtwebkit -skip qtwebkit-examples -skip qtquick1

i uruchamiam.
Jeśli wszystko przebiegło pomyślnie, pozostało nam wykonać jeszcze jeden szybki fix. Będzie to fix nieelegancki i tylko na potrzeby naszej kompilacji, bez zagłębiania się w prawidłowe rozwiązanie, które zadziała z każdym kompilatorem.

Odnajdujemy plik qtwinextras\src\winextras\winshobjidl_p.h i na samym końcu usuwamy sekcję:

#if (defined _MSC_VER && _MSC_VER < 1600) || defined(Q_CC_MINGW)

#   if !defined(__MINGW64_VERSION_MAJOR) || !defined(__MINGW64_VERSION_MINOR) || __MINGW64_VERSION_MAJOR * 100 + __MINGW64_VERSION_MINOR < 301

typedef struct SHARDAPPIDINFOLINK
{
    IShellLink *psl;        // An IShellLink instance that when launched opens a recently used item in the specified
                            // application. This link is not added to the recent docs folder, but will be added to the
                            // specified application's destination list.
    PCWSTR pszAppID;        // The id of the application that should be associated with this recent doc.
} SHARDAPPIDINFOLINK;

#   endif // !defined(__MINGW64_VERSION_MAJOR) || !defined(__MINGW64_VERSION_MINOR) || __MINGW64_VERSION_MAJOR * 100 + __MINGW64_VERSION_MINOR < 301

#endif

Usuwamy tę sekcję całkowicie. Plik zapisujemy, wracamy do głównego katalogu i zaczynamy budowanie:

mingw32-make

Zbudowane binarki będą, jak zwykle, w katalogu qtbase\bin.

Fullscreen widget w Qt4

Stworzenie widgeta pełnoekranowego w Qt4 niby jest proste, ale… okazuje się, że po drodze czyha na nas kilka pułapek. Postaram się was przez nie przeprowadzić…

W moim przypadku, mam aplikację, która wyświetla strumień video (akurat ze specjalnej karty, ale to kompletnie nieważne – może Ty chcesz oglądać np. film z pliku). Idąc za niepisanym standardem, postanowiłem opcję przejścia do fullscreen położyć na klawiszu <F11>.

QAction *theFullscreenAction = new QAction(this);
theFullscreenAction->setShortcut(QKeySequence(Qt::Key_F11));
theFullscreenAction->setCheckable(true);
connect(theFullscreenAction, SIGNAL(toggled(bool)), this, SLOT(toggleFullscreen(bool)));
addAction(theFullscreenAction);

Aby przełączyć widget, trzeba zapamiętać kilka jego właściwości, aby było do czego wracać a następnie go przełączyć. Przy powrocie, oczywiście należy odtworzyć wszystkie właściwości.

QWidget *videoFrameParent;
QLayout *videoFrameParentLayout;
Qt::WindowFlags videoFrameFlags;
QSize videoFrameSize;
void myApp::toggleFullscreen(bool bFullscreen)
	{
	if (bFullscreen)
		{
		videoFrameParent = ui.videoFrame->parentWidget();
		videoFrameParentLayout = ui.videoFrame->parentWidget()->layout();
		videoFrameFlags = ui.videoFrame->windowFlags();
		videoFrameSize = ui.videoFrame->size();

		// untie it...
		ui.videoFrame->setParent(NULL);
		ui.videoFrame->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
		ui.videoFrame->showMaximized();
		}
	else
		{
		// bring it back!
		ui.videoFrame->resize(videoFrameSize);
		ui.videoFrame->overrideWindowFlags(videoFrameFlags);
		ui.videoFrame->setParent(videoFrameParent);
		videoFrameParentLayout>addWidget(ui.videoFrame);
		ui.videoFrame->show();
		}
	}

Odpalamy aplikację i testujemy… pierwsze, co się rzuca, to nie działa klawisz powrotu. Chwila zabawy i – działa, ale trzeba Alt-Tabem przejść do osieroconego okna aplikacji głównej. Rozwiązanie jest dość proste: wystarczy naszą akcję dodać również do naszego widgetu. Dlaczego? Dlatego, że po jego wydziedziczeniu (setParent(NULL);) aplikacja główna przestaje otrzymywać zdarzenia klawiatury, gdy nasz widget ma fokus.

ui.videoFrame->addAction(theFullscreenAction);

Kolejny problem objawia się w momencie, gdy mamy konfigurację wielomonitorową i wywołamy akcję w momencie, gdy aplikacja będzie na monitorze innym, niż główny. Okaże się, że nasz fullscreen włączy się na ekranie głównym, pozostawiając osieroconą i ogołoconą aplikację główną tam, gdzie była.

Tu niestety musimy kilka linijek kodu spędzić na rozwiązanie tego problemu… sprawdzimy najpierw, gdzie nasza aplikacja się znajduje, a następnie (po wydziedziczeniu!) przesuniemy okno tam ręcznie, po czym dopiero uruchomimy showMaximized().

// untie it...
ui.videoFrame->setParent(NULL);
ui.videoFrame->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);

// hack for showing it on a screen, where the app is (in a multimonitor setup)
QDesktopWidget *dw = QApplication::desktop();
QRect drect = dw->screenGeometry(dw->screenNumber(QCursor::pos()));
int desk_x = drect.width();
int desk_y = drect.height();
int x = ui.videoFrame->width();
int y = ui.videoFrame->height();
ui.videoFrame->move(desk_x / 2 - x / 2 + drect.left(), desk_y / 2 - y / 2 + drect.top());

// yeah!
ui.videoFrame->showMaximized();

Kolejny test i… działa! 🙂 Poniżej kod w komplecie:

QWidget *videoFrameParent;
QLayout *videoFrameParentLayout;
Qt::WindowFlags videoFrameFlags;
QSize videoFrameSize;
QAction *theFullscreenAction = new QAction(this);
theFullscreenAction->setShortcut(QKeySequence(Qt::Key_F11));
theFullscreenAction->setCheckable(true);
connect(theFullscreenAction, SIGNAL(toggled(bool)), this, SLOT(toggleFullscreen(bool)));
addAction(theFullscreenAction);
ui.videoFrame->addAction(theFullscreenAction);
void myApp::toggleFullscreen(bool bFullscreen)
	{
	if (bFullscreen)
		{
		videoFrameParent = ui.videoFrame->parentWidget();
		videoFrameParentLayout = ui.videoFrame->parentWidget()->layout();
		videoFrameFlags = ui.videoFrame->windowFlags();
		videoFrameSize = ui.videoFrame->size();

		// untie it...
		ui.videoFrame->setParent(NULL);
		ui.videoFrame->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);

		// hack for showing it on a screen, where the app is (in a multimonitor setup)
		QDesktopWidget *dw = QApplication::desktop();
		QRect drect = dw->screenGeometry(dw->screenNumber(QCursor::pos()));
		int desk_x = drect.width();
		int desk_y = drect.height();
		int x = ui.videoFrame->width();
		int y = ui.videoFrame->height();
		ui.videoFrame->move(desk_x / 2 - x / 2 + drect.left(), desk_y / 2 - y / 2 + drect.top());

		// yeah!
		ui.videoFrame->showMaximized();
		}
	else
		{
		// bring it back!
		ui.videoFrame->resize(videoFrameSize);
		ui.videoFrame->overrideWindowFlags(videoFrameFlags);
		ui.videoFrame->setParent(videoFrameParent);
		videoFrameParentLayout->addWidget(ui.videoFrame);
		ui.videoFrame->show();
		}
	}

Miłego oglądania! 😉

Powered by WordPress & Theme by Anders Norén

Aby kontynuować przeglądanie tej witryny, wyraź zgodę na przechowywanie plików cookie. więcej informacji

Ta witryna używa plików cookie dla największej wygody użytkowania. Kliknij "Zaakceptuj", aby wyrazić zgodę na przechowywanie plików cookie.

Zamknij