SFML and CEGUI 0.8.3

Fabian Schlager - 06 January 2014, 16:00 - Tags: sfml cegui - 0 comments

If you ever wanted to use CEGUI with SFML you might have stumbled across this article by Laurent Gomila. Its last revision happened about a year ago, so it's kinda outdated. I've managed to use CEGUI 0.8.3 with SFML 2.1 and here's how:

Compiling & Linking

Sadly, there's no official cmake module for CEGUI, so either you write your own, use one of an existing project (like this one) or you link the libraries manually. The libraries you'll need to link is libCEGUIBase-0.so and libCEGUIOpenGLRenderer-0.so. If you need extra features, link the rest yourself (e.g. Lua support, dialogs, ...). I don't know if this concerns all Linux distributions, but at least my Arch puts CEGUI's includes into /usr/include/cegui-0, so I had to tell my compiler to use this as include directory (-I/usr/include/cegui-0).

Using CEGUI in SFML

My code layout is a bit different from the tutorial mentioned above, but you should be able to figure it out. We start with our GUI manager class:

#ifndef GAMEDESKTOP_H
#define GAMEDESKTOP_H

#include <SFML/System/Time.hpp>
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Window/Keyboard.hpp>
#include <SFML/Window/Mouse.hpp>

#include <CEGUI/CEGUI.h>
#include <CEGUI/RendererModules/OpenGL/GLRenderer.h>

class GameDesktop
{
  public:
    GameDesktop(sf::RenderWindow &screen);

    void handleEvent(const sf::Event &event);
    void update(sf::Time delta);
    void draw();

  private:
    void initializeKeyMap();
    CEGUI::Key::Scan toCEGUIKey(sf::Keyboard::Key code);
    CEGUI::MouseButton toCEGUIMouseButton(sf::Mouse::Button button);

    sf::RenderWindow& screen_;
    CEGUI::OpenGLRenderer& renderer_;

    std::map<sf::Keyboard::Key, CEGUI::Key::Scan> key_map_;
    std::map<sf::Mouse::Button, CEGUI::MouseButton> mouse_map_;
};

#endif // GAMEDESKTOP_H

All we want to keep as members is our OpenGLRenderer and the WindowManager. SFML keycodes have changed their namespace to Keyboard, so we use sf::Keyboard::Code instead of sf::Key::Code.

My implementation looks like this:

#include <SFML/Window/Event.hpp>
#include <SFML/Graphics.hpp>

#include "gamedesktop.h"

GameDesktop::GameDesktop(sf::RenderWindow &screen)
  : screen_(screen),
    renderer_(CEGUI::OpenGLRenderer::bootstrapSystem())
{
  // Set up default resource groups
  CEGUI::DefaultResourceProvider *rp = static_cast<CEGUI::DefaultResourceProvider*>(CEGUI::System::getSingleton().getResourceProvider());

  rp->setResourceGroupDirectory("schemes", "/usr/share/cegui-0/schemes/"); 
  rp->setResourceGroupDirectory("imagesets", "/usr/share/cegui-0/imagesets/");
  rp->setResourceGroupDirectory("fonts", "/usr/share/cegui-0/fonts/");
  rp->setResourceGroupDirectory("layouts", "/usr/share/cegui-0/layouts/");
  rp->setResourceGroupDirectory("looknfeels", "/usr/share/cegui-0/looknfeel");
  rp->setResourceGroupDirectory("lua_scripts", "/usr/share/cegui-0/lua_scripts/");

  CEGUI::ImageManager::setImagesetDefaultResourceGroup("imagesets");
  CEGUI::Font::setDefaultResourceGroup("fonts");
  CEGUI::Scheme::setDefaultResourceGroup("schemes");
  CEGUI::WidgetLookManager::setDefaultResourceGroup("looknfeels");
  CEGUI::WindowManager::setDefaultResourceGroup("layouts");
  CEGUI::ScriptModule::setDefaultResourceGroup("lua_scripts");

  // Set up the GUI
  CEGUI::SchemeManager::getSingleton().createFromFile("WindowsLook.scheme");
  CEGUI::FontManager::getSingleton().createFromFile("DejaVuSans-10.font");

  CEGUI::System::getSingleton().getDefaultGUIContext().getMouseCursor().setDefaultImage("WindowsLook/MouseArrow");

  CEGUI::WindowManager& wmgr = CEGUI::WindowManager::getSingleton();
  CEGUI::Window *root = wmgr.createWindow("DefaultWindow", "root");
  root->setProperty("MousePassThroughEnabled", "True");
  CEGUI::System::getSingleton().getDefaultGUIContext().setRootWindow(root);

  CEGUI::FrameWindow *fw = static_cast<CEGUI::FrameWindow*>(wmgr.createWindow("WindowsLook/FrameWindow", "testWindow"));

  root->addChild(fw);
  fw->setText("Hello World!");

  // Initialize SFML-to-CEGUI key mapping
  initializeKeyMap();
}

void GameDesktop::handleEvent(const sf::Event &event)
{
  bool handled = false;
  CEGUI::GUIContext& context = CEGUI::System::getSingleton().getDefaultGUIContext();
  sf::Vector2i mouse_pos;

  switch (event.type)
  {
  case sf::Event::TextEntered:
    handled = context.injectChar(event.text.unicode); break;
  case sf::Event::KeyPressed:
    handled = context.injectKeyDown(toCEGUIKey(event.key.code)); break;
  case sf::Event::KeyReleased:
    handled = context.injectKeyUp(toCEGUIKey(event.key.code)); break;
  case sf::Event::MouseMoved:
    mouse_pos = sf::Mouse::getPosition(screen_);
    handled = context.injectMousePosition(static_cast<float>(mouse_pos.x), static_cast<float>(mouse_pos.y));
    break;
  case sf::Event::MouseButtonPressed:
    handled = context.injectMouseButtonDown(toCEGUIMouseButton(event.mouseButton.button)); break;
  case sf::Event::MouseButtonReleased:
    handled = context.injectMouseButtonUp(toCEGUIMouseButton(event.mouseButton.button)); break;
  case sf::Event::MouseWheelMoved:
    handled = context.injectMouseWheelChange(static_cast<float>(event.mouseWheel.delta)); break;
  case sf::Event::Resized:
    CEGUI::System::getSingleton().notifyDisplaySizeChanged(CEGUI::Sizef(event.size.width, event.size.height)); break;
  }

  // If mouse is over an UI element, we do not initiate drag scrolling
  if (!handled)
  {
    // we can handle this event here, CEGUI didn't use it ...    
  }
}

void GameDesktop::update(sf::Time delta)
{
  CEGUI::System::getSingleton().injectTimePulse(delta.asSeconds());
}

void GameDesktop::draw()
{
  // undo all transformations SFML did
  screen_.resetGLStates();
  CEGUI::System::getSingleton().renderAllGUIContexts();
}

void GameDesktop::initializeKeyMap()
{
  key_map_[sf::Keyboard::A]         = CEGUI::Key::A;
  key_map_[sf::Keyboard::B]         = CEGUI::Key::B;
  key_map_[sf::Keyboard::C]         = CEGUI::Key::C;
  // (...)

  mouse_map_[sf::Mouse::Left]       = CEGUI::LeftButton;
  // (...)
}

CEGUI::Key::Scan GameDesktop::toCEGUIKey(sf::Keyboard::Key code)
{
  return key_map_[code];
}

CEGUI::MouseButton GameDesktop::toCEGUIMouseButton(sf::Mouse::Button button)
{
  return mouse_map_[button];
}

The constructor sets everything up, including paths used by CEGUI's theme and texture system. You might want to change these paths in your project. It's important to correctly set up your root window if you want to handle more than GUI events in the class GameDesktop. That's why we why call:

// (...)
root->setProperty("MousePassThroughEnabled", "True");
// (...)

I cut the initializeKeyMap() function, either download the source or look here. The important parts are in handleEvents() and update(). The first one handles the event injection CEGUI needs, to function properly, as the library itself has no way of handling input. This includes resizing of the RenderWindow. Instead of injecting our events directly into CEGUI::System, we now use a GUIContext.

CEGUI also needs to be informed about time to update animations etc. We solve this by injecting time pulses in update():

// (...)
CEGUI::System::getSingleton().injectTimePulse(delta.asSeconds());
// (...)

When drawing, we need to reset all transformations SFML has performed, to keep our windows from getting transformed too:

// undo all transformations SFML did
screen_.resetGLStates();
CEGUI::System::getSingleton().renderAllGUIContexts();

And that's it. Pretty straight forward, isn't it? :)

You can get the complete source code here. Compile it using:

$ g++ -g -o test -std=c++11 -I/usr/include/cegui-0 *.cpp -lsfml-window -lsfml-system -lsfml-window -lsfml-graphics -lCEGUIBase-0 -lCEGUIOpenGLRenderer-0

If you have questions, find bugs or want to say something else feel free to leave a comment.

Read more!

Read Onleihe Books On Linux

Fabian Schlager - 25 December 2013, 14:00 - Tags: ebooks drm epub - 0 comments

The Good

Onleihe is a provider for digital libraries, used by, for example, Stadtbücherei Salzburg. Once registered, people can borrow eBooks for free. So far, so good.

The Bad

eBooks can only be borrowed for 14 days and have to be downloaded using Adobe's Digital Editions. On top of that, only one person can borrow a book at a time, there is only one virtual copy of each book. Welcome to the digital age!

The Ugly

As mentioned above, your eBooks have to be downloaded using a proprietary tool made by Adobe. You have to register with them to use their crappy Digital Editions. Remember when hackers broke into their servers and stole 152 million usernames, passwords and credit card numbers? That was fun. So be sure to use a unique password, but you should do that anyway. Of course, this tool does not support Linux natively, wine seems to work. The eBooks are encrypted with Adobes ADEPT DRM scheme (basically, each book is encrypted using AES, each user gets an RSA key which can decrypt the AES key, more information here). To decrypt books, you have to get the users key first. Bad news: It's not that easy to extract. Good news: Someone already wrote tools to do that. Yay, Open Source.

A more or less automated solution

The first few steps extract your key Install Digital Editions using winetricks:

$ winetricks adobe_diged

Important: Start Adobe Digital Editions and set everything up (Activate your computer with your Adobe account):

$ export WINEPREFIX=$HOME/.local/share/wineprefixes/adobe_diged
$ cd "$WINEPREFIX/drive_c/Program Files (x86)/Adobe/Adobe Digital Editions" 
$ wine digitaleditions.exe

Download "Tools" from here. This contains a Calibre plugin that can extract your key. Theoretically, this should work with the Linux version of Calibre (utilizing wine) but I could not get that to work, so I'm going to set up Calibre in the same WINEPREFIX as Digital Editions.

Download the windows version of Calibre. Install it like that:

$ wine msiexec /i calibre-1.16.0.msi

Copy the plugin from the Tools archive to the WINEPREFIX:

$ cp DeDRM_calibre_plugin/DeDRM_plugin.zip $WINEPREFIX/drive_c

Start Calibre and add the plugin (Preferences - Plugins - Load plugin from File - Select DeDRM_plugin.zip). Now select the DeDRM plugin (under "File type plugins") and press "Customize plugin". Select "Adobe Digital Editions ebooks". A dialog should pop up. Just press the green plus symbol, leave everything as is and add the key. Then select the key in the list, press the fourth icon and save your key anywhere you can find it later.

Now go and download the script ineptepub.py (mirror)

Starting it without parameters brings up a dialog window. For CLI fans, just call it like this:

$ python2.7 ineptepub.py path/to/default_key.der path/to/drm.epub path/to/target.epub

And voila! We now have a DRM free epub.

You can use the following script to somehow automate the fetching and decrypting of eBooks:

TBD

Read more!

Using pelican with git

Fabian Schlager - 05 January 2013, 15:00 - Tags: blog pelican git python - 0 comments

In this post, I'll try to explain how to set up a simple blog using Pelican with git.

First off, get Pelican and install it. You'll probably need some third-party Python modules like Markdown, Pygments (for source code highlighting). Using the following command, Python will get all mandatory packages for you:

$ python setup.py install

Create a bare git repository on your remote machine:

$ git init --bare myblog.git

Next to the directory myblog.git, create another directory called e.g. "tmp". Edit myblog.git/hooks/post-receive (create it, if it is not there) and paste the following lines:

#!/bin/sh

BLOG_DIR="/home/blog/tmp"
TARGET_DIR="/var/www/blog"
THEME="simple"

GIT_WORK_TREE=$BLOG_DIR git checkout -f
pelican -v -d -t $THEME -o $TARGET_DIR -s $BLOG_DIR/settings.py $BLOG_DIR/articles

Save and chmod +x. This hook will be executed everytime you push something to your remote repository. Make sure that both BLOG_DIR and TARGET_DIR exist and you have sufficient permissions to access them before you continue!

Now, on your local machine, clone the repos. Add the file settings.py (for an explanation on how to create this, see the Pelican docs). Create a directory articles and place a test article in it:

Author: Your Name
Tags: Some, cool, tags
Title: Testpage

This is just a test, relax!

Save as test.md, add all the files to the repository, commit and push. You should see some output from Pelican, and if everything worked as it should, you can view your first test article.

Read more!