Git a automatyczne budowanie Jenkinsem

Do tej pory, na cele Continuous Integration, w Jenkinsie miałem ustawione sprawdzanie repozytorium Git (opcja SCM polling) co 15 minut. W zasadzie to działało, ale bywały momenty, że owe 15 minut było okresem zbyt długim. Można oczywiście (nikt nie broni…) ustawić okres sprawdzania na * * * * *, czyli sprawdzanie co minutę, ale to również nie jest dobre rozwiązanie, z oczywistych powodów. Dlatego też postanowiłem powiesić akcję odpytywania Gita przez Jenkinsa na haku, żeby automagicznie każdy push od developera powodował start Jenkinsa.

Oczywiście, Jenkins musi być wyposażony we wtyczkę Git.

Zaczynamy od próby ręcznego zdalnego wywołania Jenkinsa. Otóż wtyczka Git otwiera takową drogę właśnie na okoliczność notyfikacji o nowym commicie. Jak czytamy w dokumentacji wtyczki, można powiadomić Jenkinsa o konkretnym commicie, ale ja na cele Continuous Integration i tak chcę pobrać najnowsze w danej chwili źródła, aby sprawdzić integrację poszczególnych prac właśnie. Dlatego użyjemy najprostszej formy wywołania wtyczki, która spowoduje wymuszenie odpytania repozytorium o zmiany.

Na początek, w konfiguracji zadania Jenkinsowego, usuwamy wszelkie terminarze, jakie mieliśmy – ale samą opcję Poll SCM musimy pozostawić włączoną (ale pustą):

Jenkins SCM Polling

Następnie, będąc na serwerze gitowym, instalujemy pakiet curl, jeśli go tam jeszcze nie ma. Gdy już go mamy, wywołujemy testową komendę:

curl http://moj.jenkins.com:8080/git/notifyCommit?url=git@moj.git.com:WspanialeRepo

Ścieżka do repozytorium powinna być dokładnie taka sama, jaką podajemy przy jego klonowaniu. Jeśli wszystko pójdzie dobrze, przeczytamy, że zostało zlecone odpytanie repozytorium. Sprawdzamy w logach Jenkinsa Git Polling Log, czy rzeczywiście tak było.

Jeśli wszystko zadziałało, czas ten proces zautomatyzować. Aby wywołać jakąś akcję po każdym pushu od developera, musimy zdefiniować hak post-receive. W katalogu repozytorium znajduje się podkatalog hooks. Tworzymy nowy plik i nadajemy mu właściwe uprawnienia:

touch post-receive
chown git:git post-receive
chmod 775 post-receive

i edytujemy go:

#!/bin/sh
curl http://moj.jenkins.com:8080/git/notifyCommit?url=git@moj.git.com:WspanialeRepo

Od tej pory, za każdym razem, gdy developer zrobi pusha, Jenkins wykona swoją robotę 🙂 A my cieszymy się z Continuous Integration 😉

Facebooktwittergoogle_plusredditpinterestlinkedinmail
twitterlinkedin

Android – dobór koloru tekstu pod kolor tła

Gdy dodajemy tekst na jakimś tle, którego kolor ustawiany jest dynamicznie, mamy problem: jaki kolor tekstu? Czarny czy biały?

Aby rozwiązać ten problem, należy sprawdzić, jak jasny jest kolor tła. Problem w tym, że wpływ poszczególnych komponentów RGB na ogólną jasność jest różna, stąd trzeba ową jasność policzyć uwzględniając tę zasadę. Ja użyłem dość popularnej w internecie formuły – i spisuje się naprawdę dobrze.

    /**
     * Find the right color of the text, depending on the color of the background.
     * @param aColor Color, to which we're adjusting by contrast.
     * @return Black or white color.
     */
    public int getBlackOrWhite(int aColor)
    {
        int red = (aColor >> 16) & 0xFF;
        int green = (aColor >> 8) & 0xFF;
        int blue = (aColor >> 0) & 0xFF;
        int result = 0;
        if (((double)red*0.299 + (double)green*0.587 + (double)blue*0.114) > 186)
            result = Color.BLACK;
        else
            result = Color.WHITE;
        return result;
    }

Parametrem wejściowym jest kolor tła, wartość zwracana to kolor czarny lub biały. Formuła doboru wygląda na dobrą, bo spośród tych kolorów, które przetestowałem, wybór: czarny-biały zawsze był trafny, stąd mogę ją z czystym sumieniem polecić – co też czynię.

Facebooktwittergoogle_plusredditpinterestlinkedinmail
twitterlinkedin

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.

Facebooktwittergoogle_plusredditpinterestlinkedinmail
twitterlinkedin

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()-&gt;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! 😉

Facebooktwittergoogle_plusredditpinterestlinkedinmail
twitterlinkedin

Android: pełna obsługa multitouch w grze

Dodać do programu (gry) obsługę dotyku pod Androidem jest niezmiernie prosto. Wystarczy kilka linijek kodu:

 

@Override
public boolean onTouchEvent(MotionEvent event)
{
	float x = event.getX();
	float y = event.getY();

	switch (event.getAction())
	{
	case MotionEvent.ACTION_DOWN:
		processTouchDown(x, y);
		break;
	case MotionEvent.ACTION_UP:
		processTouchUp(x, y);
		break;
	}		
	return false;
}

 

Schody zaczynają się w momencie, gdy chcemy zrobić np. pełne sterowanie postacią – a jego ramach np. ruch oraz strzelanie. Już przy pierwszym teście dochodzimy do wniosku, że chcemy obsługiwać wiele palców naraz, niezależnych od siebie.

 

Z pomocą przychodzi nam mechanizm dodany w API Level 8, czyli od Androida 2.2.x Froyo. Każdy event dotyku zawiera w sobie pełną informację o wszystkich ewentualnych punktach nacisku, ich pozycji itd. Dlatego, naszą obsługę zdarzeń rozwiniemy tak:

 

private int actionMovementId = -1;
private int actionFiringId = -1;

 

@Override
public boolean onTouchEvent(MotionEvent event)
{
	int actionId = event.getActionIndex();
	float x = event.getX(actionId);
	float y = event.getY(actionId);

	switch (event.getActionMasked())
	{
	case MotionEvent.ACTION_DOWN:
	case MotionEvent.ACTION_POINTER_DOWN:
		processTouchDown(x, y, actionId);
		break;
	case MotionEvent.ACTION_UP:
	case MotionEvent.ACTION_POINTER_UP:
		processTouchUp(x, y, actionId);
		break;
	}

	return false;
}

 

private void processTouchDown(float x, float y, int actionId)
{
	Point displaySize = new Point(CgEngine.display.getWidth(), CgEngine.display.getHeight());
	int touchableArea = displaySize.y * 3 / 4;
	int halfX = displaySize.x / 2;

	if (y &gt; touchableArea)
	{
		// fire!
		if (x &lt; halfX)
		{
			CgEngine.playerFireAction = CgEngine.PLAYER_FIRE_SHOOT;
			actionFiringId = actionId;
		}
		// steering
		else
		{
			if (x &lt; halfX * 1.5)
				CgEngine.playerRunAction = CgEngine.PLAYER_TURN_LEFT_1;
			else
				CgEngine.playerRunAction = CgEngine.PLAYER_TURN_RIGHT_1;

			actionMovementId = actionId;
		}
	}
}

 

private void processTouchUp(float x, float y, int actionId)
{
	if (actionFiringId == actionId)
	{
		CgEngine.playerFireAction = CgEngine.PLAYER_FIRE_HOLD;
		actionFiringId = -1;
	}
	if (actionMovementId == actionId)
	{
		CgEngine.playerRunAction = CgEngine.PLAYER_RELEASE;
		actionMovementId = -1;
	}
}

 

Jak widać, przy rozpoczynaniu każdej akcji, musimy zapamiętać identyfikator palca, który ją uruchomił – później, gdy przyjdzie event puszczenia palca, będzie można łatwo zidentyfikować, która akcja powinna się zakończyć.

 

Powodzenia!

 

Facebooktwittergoogle_plusredditpinterestlinkedinmail
twitterlinkedin