Posts Tagged 'Vala'
2012-03-16

While hacking on MuttonChop, It was necessary to create an HTTP server to handle requests from the Web client. Since the server could be quite useful for various other projects, I though it would be a good idea to take out the MuttonChop specific code and share the Server as a building block for future projects.

Originally, the server was going to use libsoup, an HTTP client/server library, for all of my HTTP serving needs but I couldn't get libsoup to create Server Sent Events so I instead opted to use GIO for low level socket communication and I had to roll my own server.

Enter the Vala

/* A Little Vala Server
 * copyright 2012 Jezra Lickter
 * Licensed GPLv3
 
 * save as "valaserver.vala" and compile with
 * valac --pkg gio-2.0 valaserver.vala
 
 */

using GLib;
//what port are we serving on?
const uint16 PORT 8787;

namespace StatusCode {
  const string FILE_NOT_FOUND "HTTP/1.1 404 Not Found\n"
  const string OK "HTTP/1.1 200 OK\n"
  const string ERROR "HTTP/1.1 500 Internal Server Error\n"
}

struct Request {
  string full_request;
  string path;
  string query;
  HashTable<stringstringargs;
  string object;
  string action;
  string val;
}

struct Response {
  string status_code;
  string content_type;
  string text;
  uint8[] data;
}

public class WebServer {
  
  private ThreadedSocketService tss;
  private string public_dir;
  private Regex ext_reg;
  
  public WebServer() {
    public_dir="public";
    try {
      ext_reg new Regex("\\.(?<ext>[a-z]{2,4})$");
    catch(Error e) {
      stderr.printf(e.message+"\n");
    }
    //make the threaded socket service with hella possible threads
    tss new ThreadedSocketService(150);
    //create an IPV4 InetAddress bound to no specific IP address
    InetAddress ia new InetAddress.any(SocketFamily.IPV4);
    //create a socket address based on the netadress and set the port
    InetSocketAddress isa new InetSocketAddress(iaPORT);
    //try to add the address to the ThreadedSocketService
    try {
      tss.add_address(isaSocketType.STREAMSocketProtocol.TCPnullnull);
    catch(Error e) {
      stderr.printf(e.message+"\n");
      return;
    }
    /* connect the 'run' signal that is emitted when 
     * there is a connection to our connection handler
     */
    tss.run.connectconnection_handler );
  }
  
  public void run() {
    //we need a gobject main loop
    MainLoop ml new MainLoop();
    //start listening 
    tss.start();
    stdout.printf(@"Serving on port $PORT\n");
    //run the main loop
    ml.run();
  }
  //when a request is made, handle the socket connection
  private bool connection_handler(SocketConnection connection) {
    string first_line ="";
    size_t size 0;
    Request request Request();
    //get data input and output streams for the connection
    DataInputStream dis new DataInputStream(connection.input_stream);
    DataOutputStream dos new DataOutputStream(connection.output_stream);  
    //read the first line from the input stream
    try {
      first_line dis.read_lineout size );
      request get_requestfirst_line );
      
    catch (Error e) {
      stderr.printf(e.message+"\n");
    }
    //build a response based on the request
    Response response Response();
    response get_file_response(request);
    serve_responseresponsedos );
    return false;
  }
  
  private void serve_response(Response responseDataOutputStream dos) {
    try {
      var data response.data ?? response.text.data;
      dos.put_string(response.status_code);
      dos.put_string("Server: ValaSocket\n");
      dos.put_string("Content-Type: %s\n".printf(response.content_type));
      dos.put_string("Content-Length: %d\n".printf(data.length));
      dos.put_string("\n");//this is the end of the return headers
      /* For long string writes, a loop should be used,
       * because sometimes not all data can be written in one run 
       *  see http://live.gnome.org/Vala/GIOSamples#Writing_Data
       */ 
      long written 0;
      while (written data.length) { 
          // sum of the bytes of 'text' that already have been written to the stream
          written += dos.write (data[written:data.length]);
      }
    catchError e ) {
      stderr.printf(e.message+"\n");
    }
  }
  
  private Response get_file_response(Request request) {
    //default request.path = index.htm
    string request_path = (request.path=="/") ? "index.htm" request.path;
    string filepathPath.build_filename(public_dirrequest_path);
    Response response Response();
    response.content_type "text/plain";
    response.status_code StatusCode.ERROR;
    //does the file exist?
    if (FileUtils.test(filepathGLib.FileTest.IS_REGULAR) ) {
      //serve the file
      bool read_failed true;
      uint8[] data = {};
       try {
        FileUtils.get_data(filepathout data);
        response.data data;
        response.content_type get_content_typefilepath );
        response.status_code StatusCode.OK;
        read_failed false;
      catch (Error err) {
        response.text err.message;
        response.status_code StatusCode.ERROR;
        response.content_type="text/plain";
      }    
    else {
      //file not found
      response.status_code StatusCode.FILE_NOT_FOUND;
      response.content_type "text/plain";
      response.text "File Not Found";
    }
    return response;
  }
  
  private string get_content_type(string file) {
    //get the extension
    MatchInfo mi;
    ext_reg.matchfile0out mi );
    var ext mi.fetch_named("ext");
    string content_type "text/plain";
    if (ext!=null) {
      string lower_ext ext.down();
      switch(lower_ext) {
        case "htm":
        case "html":
          content_type="text/html";
          break;
        case "xml":
          content_type="text/xml";
          break;
        case "js":
        case "json":
          content_type="text/javascript";
          break;
        case "css":
          content_type="text/css";
          break;
        case "ico":
          content_type="image/icon";
          break;
        case "png":
          content_type="image/png";
          break;
        case "jpg":
          content_type="image/jpeg";
          break;
        case "gif":
          content_type="image/gif";
          break;
      }
    }
    return content_type;
  }
  
  // return a Request based on a portion of th line
  private Request get_request(string line) {
    Request Request();
    r.args new HashTable<stringstring>(str_hashstr_equal);
    //get the parts from the line
    string[] parts line.split(" ");
    //how many parts are there?
    if (parts.length == 1) {
      return r;
    }
    //add the path to the Request
    r.full_request parts[1];
    parts r.full_request.split("?");
    r.path parts[0];
    r.query parts[1] ?? "";
    //get the object and action
    parts r.path.split("/");
    if (parts.length 1) {
      r.object parts[1] ?? "";
    }
    if (parts.length 2) {
      r.action parts[2] ?? "";
    }
    if (parts.length 3) {
      r.val Uri.unescape_string(parts[3]) ?? "";
    }
    //split the query if it exists
    if (r.query != "") {
      string[] query_parts={};
      parts r.query.split("&");
      foreachstring part in parts ) {
        query_parts part.split("=");
        if (query_parts.length == 2){
          r.args[query_parts[0]] = Uri.unescape_string(query_parts[1]);
        }
      }
    }
    return r;
  
}

public static void main() {
  WebServer ws new WebServer();
  ws.run();
}

The code is also available at http://hoof.jezra.net/snip/nV. save as "valaserver.vala" and compile with

valac --pkg gio-2.0 valaserver.vala

In the same directory where the code was compiled, create a directory named "public" and fill the directory with static HTML files. Point your browser to http://localhost:8787 to see the server in actions.

note: The default file needs to be named "index.htm"

When an HTTP request is parsed, the Request struct splits the request into controller,action,val variables based on the location of text in the requested URL. The format is host:port/controller/action/val.

In MuttonChop these variables are processed with a series of "switch" statements in order to determine what MuttonChop is supposed to do. For example to to set the player volume to 77, the controller is 'player', the action is 'volume' and the val is '77', thus the URL would be host:port/player/volume/77.

Sweet Sauce!

Comments
2012-10-19 Adrian:
very nice, thank you.

I am desperatly trying to write a webserver whith SSL Support, but i have no idea how to solve that.

I woul be _VERY_ grateful for any hints or samples.
2012-10-19 jezra:
Hi Adrian, unfortunately, I have no experience writing an SSL webserver, so you are going to have to slog through some documentation to find your solution.

Ewww, that sound far to much like I just told you to RTFM. sorry buddy.
2012-10-22 Adrian:
alright, but thx anyway!

:-)
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
7 minus 4
Answer:
required
2012-03-01

Hey! That crummy file downloader written in Vala doesn't work with the Rathole Radio Ogg feed. What gives?

I'm glad you asked. In higher level programming languages, it is quite common to have the ability to read chunks of data from an HTTP stream when using a module or library designed to handle HTTP traffic. However, since the Vala based downloader uses low-level sockets to read from the stream, it is up to the developer to implement reading of Chunked HTTP data.

In parsing terms, the format is quite simple:

  1. Read a line from the steam. This will be a number and it represents the number of bytes of data in the chunk. We'll call it "x"
  2. Read "x" number of bytes from the next line. This is a "chunk" of data.
  3. repeat until "x" is equal to 0

While that should be nice and easy, the number of bytes that are supposed to be read is in hexadecimal and I couldn't find a Vala function to convert a hexadecimal string into an integer value. After spending way too much time searching for a solution, I took the DIY approach and hacked together a few functions to do exactly what I needed to do.

So without further ado, here is my Vala code to convert a hexadecimal string to an integer.

Enter the Vala

public int hexvalstring ) {
  switch(c) {
    case "a":
    return 10;
    case "b":
    return 11;
    case "c":
    return 12;
    case "d":
    return 13;
    case "e":
    return 14;
    case "f":
    return 15;
    default:
    return int.parse(c);
  }
}

public int64 hextoint(string hex){
  //convert the string to lowercase
  string hexdown hex.down();
  //get the length of the hex string
  int hexlen hex.length;
  int64 ret_val 0;
  string chr;
  int chr_int;
  int multiplier;
  
  //loop through the string 
  for (int 0hexlen i++) {
    //get the string chars from right to left
    int inv = (hexlen-1)-i;
    chr hexdown[inv:inv+1];
    chr_int hexval(chr);
    
    //how are we going to multiply the current characters value?
    multiplier 1;
    for(int j++) {
      multiplier *= 16;
    }
    ret_val += chr_int multiplier;
  }
  return ret_val;
}

public static void main() {
  string[] hexvals = {"9AC3","1234","b525","abcdef","bbb"};
  int64 i;
  foreachstring hexval in hexvals) {
    hextoint(hexval);
    stdout.printf(@"$hexval = $i\n");
  }
}

Only the hextoint and hexval functions are needed, but I though it would be nice to add a bit of code to use the fuctions.

Alright, now that that is done, it's time to implement the reading of Chunked data in the downloader so that eventually I will be able to add audcatching functionality to MuttonChop.

Comments
2012-03-23 okin56:
moi je fais comme ça.
c'est plus concis

<code>
public int hexval(string c)
{
string ref = "0123456789abcdef";
return ref.index_of(c);
}
</code>
2012-03-23 jezra:
merci!
2012-11-20 Luca Bruno:
long n = "deadbeef".to_long(null,16);
2012-11-29 gilzad:
Strange enough the current compiler (v0.18) warns that str.to_long(..) is deprecated and one should use long.parse() instead. But in the current vapi long.parse does not support any base specification.
I hope there will be a substitute before the deprecated version vanishes.
2012-11-30 Luca Bruno:
It's reported and I think there will be an alternative for sure in the future: https://bugzilla.gnome.org/show_bug.cgi?id=656691
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
6 minus 3
Answer:
required
2012-02-18

While hacking away on MuttonChop it became rather obvious that I would need to be able to download files from the internet in order to listen to serialized audio and video recording broadcasts.

Hmmmm, audio + broadcast, I'll call it an audcast. Yea yea, I know, most people call it a podcast, but I have no idea where "pod" comes from so I'll call it as I see it.

Actually, I don't really need to download the files because MuttonChop uses Gstreamer for media playback and Gstreamer is fully capable of streaming media over HTTP. However, since I don't hate the organizations that are serving the files, I'll try to limit the amount of bandwidth that MuttonChop consumes.

Alright, enough yammering, let's get to the code
Enter the Vala

/* 
 * copyright 2012 Jezra Lickter
 * Licensed GPLv3
 
 */
using GLib;

public class ParsedURL {
  public string scheme;
  public string host;
  public int port;
  public string request;
  public ParsedURL(string url) {
    request "";
    //parse that shit!
    string[] bits url.split("://");
    scheme bits[0];
    if (scheme.down() == "http" )
      port 80;
    if (scheme.down() == "ftp" )
      port 21;
    bits bits[1].split("/");
    string hp bits[0];
    string[] hpbits hp.split(":");
    host hpbits[0];
    if (hpbits.length ){
      port int.parse(hpbits[1]);
    }
    if(bits.length 1) {
      for (int i=i<bits.lengthi++) {
        request+="/"+bits[i];
      }
    else {
      request "/";
    }
    stdout.printf(@"$scheme $host $port $request\n");
  }
}

public class Header {
  Regex key_value_regex;
  public string status;
  public string Date;
  public string Server;
  public string Location;
  public uint64 Content_Length;
  public string Content_Type;
  public string code;
  public Header() {
    try {
      key_value_regex new Regex("(?P<key>.*):\\s+(?P<value>.*)");
    catch (RegexError err) {
      stderr.printf(err.message+"\n");
    }
  }
  public void add_valuestring line ){
    if(status==null) {
      status line;
      var line_bits status.split(" ");
      code line_bits[1];
    else {
      MatchInfo match_info;
      string key,val
      ifkey_value_regex.match(line,GLib.RegexMatchFlags.ANCHOREDout match_info) ) {
        key match_info.fetch_named("key");
        val match_info.fetch_named("value");
        switch (key) {
          case "Date" :
            Date val;
            break;
          case "Server":
            Server val;
            break;
          case "Location":
            Location val;
            break;
          case "Content-Length":
            Content_Length uint64.parse(val);
            break;
          case "Content-Type":
            Content_Type val;
            break;
          default:
            break;
        }
      }
    }
  }
}

public class Downloader Object {
  private uint64 length;
  private uint64 bytes_read;
  private int dl_length;
  private int throttler;
  private bool download_complete;
  private bool downloader_finished;
  private string url;
  private bool kill_download;
  private string local_file_path;
  private bool borked;
  public Downloaderstring urlstringlocal_file=nullintthrottler=0) { 
    borked false;
    length 0;
    dl_length 0;
    this.throttler throttler;
    download_complete false;
    downloader_finished false;
    this.url url;
    kill_downloadfalse;
    if (local_file != null) {
      local_file_path local_file;
    else {
      local_file_path Path.get_basename(url);
    
    downloadurl );
  }
  
  public bool is_borked() {
    return borked;
  }
  
  void download(string remoteintredirects 10) {
    ParsedURL pu new ParsedURL(remote);
    /* http://live.gnome.org/Vala/GIONetworkingSample */
    try {
      // Resolve hostname to IP address
      var resolver Resolver.get_default ();
      var addresses resolver.lookup_by_name (pu.hostnull);
      var address addresses.nth_data (0);

      // Connect
      var client new SocketClient ();
      var conn client.connect (new InetSocketAddress (address, (uint16)pu.port));

      // Send HTTP GET request
      var message "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n".printf(pu.requestpu.host);
      conn.output_stream.write (message.data);

      // Receive response
      var response new DataInputStream (conn.input_stream);
      size_t length=0;
      string line;
      Header header new Header();
      do {
        line response.read_line(out length);
        header.add_value(line);
      }while(length );  
      //check and handle redirects 
      if(header.code == "301" || header.code=="302") {
        //how many redirects is this?
        if redirects<=) {
          stderr.printf("Error: Too many Redirects\n");
        else {
          //download from the redirect location
          downloadheader.Locationredirects--);
          return;
        }
      }
      //where is this thing going?
      string partial_file local_file_path+".partial";
      var file File.new_for_path (partial_file);
      // delete file if it already exists
      if (file.query_exists ()) {
          file.delete ();
      }
      var dos new DataOutputStream (file.create (FileCreateFlags.REPLACE_DESTINATION));
      //that's it for the headers
      bytes_read 0;
      uchar[] bytes;
      while ( !conn.is_closed () && bytes_read header.Content_Length ) {
        //clear the bytes array
        bytes = {};
        bytes += response.read_byte();
        bytes_read++;
        //write the byte
        dos.write (bytes);
        //TODO: sleep the thread if throttling
      }
      //close the output stream
      dos.close();
      //move the partial file to the finished file
      FileUtils.rename(partial_filelocal_file_path);
      
    catch (Error e) {
        stderr.printf ("%s\n"e.message);
        return ;
    }
  }
}
public static void mainstring[] args) {
  string url "";
  string local_file "";
  bool die false;
  Downloader d;
  switchargs.length ) {
    case :
      local_file args[2];
      url args[1];
      new Downloader(urllocal_file);
      break;
    case :
      url args[1];
      new Downloader(url);
      break;
    default:
      stdout.printf("--USAGE--\n");
      stdout.printf("%s REMOTE_URL [LOCAL_FILENAME]\n"args[0]);
      die true;
      break;
  
  if die ) {
    return;
  }
}

The code is also available in the hoof at http://hoof.jezra.net/snip/nU. Save the code as "downloader.vala" and compile with

valac --pkg gio-2.0 downloader.vala

Because this code is destined to be part of a larger project, there will be parts of the code that currently aren't being used, but will be necessary in the future... and I'm too lazy to remove the unused bits for this posting.

Similarly, the code hasn't been tested with FTP. Oh well, it does what I need it to do, and that is the important thing.

Comments
2013-03-22 Nasser:
i am new in linux and php, i am .net programmer and i am learning vala and php, thanks for this code, hove i can use this code in a sample application? or is its download jobs, restartable?
2013-03-22 jezra:
Hello Nasser,
This is experimental code that was the basis for the downloader in MuttonChop.

I'm going to suggest downloading the MuttonChop code https://code.launchpad.net/muttonchop and looking at the downloader application in the 'test_apps' directory.
2018-01-25 Manish Jain:
Hello Mr. Jezra,

I am writing a book on modern Unix (FreeBSD / Linux), which has a dedicated section for Vala programming. I would like to include your File downloader code as a code snippet (duly credited to you and linked to this URL).

Would that be okay with you ? I would be grateful if you could send me an email mentioning your approval.

Thanks
Manish Jain
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
9 minus 7
Answer:
required
2012-01-16

The Basics

While researching Push methods that would allow me to push data from a server to a client, I learned about Server Sent Events and decided to go about writing a bit of code to test the technology.

Why?

For a current project with a web based UI, I have an AJAX request update the UI every 2 seconds with data requested from the application. Most of the requested data doesn't change very often, but the data that does change, changes fast enough that 2 seconds causes a lot of latency in UI updates.

The project currently uses libsoup for creating a web server. Unfortunately, I couldn't get my current libsoup code to work the way I needed it to, so for this test, I wrote a fairly simple/limited webserver using GIO sockets.

It isn't a very proper webserver, but it is a decent example of coding in vala with sockets, and it does a fine job of showing off Server Sent Events.

Enter the Vala

using GLib;
//what port should this serve on?
const uint16 PORT 7777;
//when a request is made, handle the socket connection
bool connection_handler(SocketConnection connection) {
  string first_line ="";
  size_t size 0;
  string request "";
  //get data input and output strings for the connection
  DataInputStream dis new DataInputStream(connection.input_stream);
  DataOutputStream dos new DataOutputStream(connection.output_stream);  
  //read the first line from the input stream
  try {
    first_line dis.read_lineout size );
    request get_requestfirst_line );
  catch (Error e) {
    stderr.printf(e.message+"\n");
  }
  //do stuff based on the request
  switchrequest ) {
    case "/" :
      try {
        // create the html and javascript that will call /sse
        string html "<html>
          <head>
            <title>Soup Server Sent Event</title>
            <script type='text/javascript'>
              function init() {
                var info_div document.getElementById('info_div');
                var source new EventSource('/sse');
                source.onmessage function (event) {
                  info_div.innerHTML event.data;
                };
              }
            </script>
          </head>
          <body onload='init();'>
            <div id='info_div'></div>
          </body>
        </html>";
        dos.put_string("HTTP/1.1 200 OK\n");
        dos.put_string("Server: ValaSocket\n");
        dos.put_string("Content-Type: text/html\n");
        dos.put_string("Content-Length: %d\n".printf(html.length));
        dos.put_string("\n");//this is the end of the return headers
        dos.put_string(html);
      catch(Error e) {
        stderr.printf(e.message+"\n");
      }
      break;
    case "/sse" :
      try {
        dos.put_string("HTTP/1.1 200 OK\n");
        dos.put_string("Server: ValaSocket\n");
        dos.put_string("Content-Type: text/event-stream\n");
        dos.put_string("\n");
      catch(Error e) {
        stderr.printf(e.message+"\n");
      }
      string info;
      int count 0;
      bool looping true;
      while(looping) {
        count++;
        if (count 10 ) {
          count 1;
        }
        info @"data: hello world $count\n\n";
        try {
          dos.put_string(info);
          //sleep for $count half seconds
          Thread.usleep(count*500000);
        }catch(Error e) {
          //is the pipe broken?
          looping false;
        }
      }
      break;
    default:
      //404
      try {
        dos.put_string("HTTP/1.1 404\n");
        dos.put_string("Server: ValaSocket\n");
        dos.put_string("Content-Type: text/plain\n");
        dos.put_string("\n");
        dos.put_string("File Not Found");
      catch(Error e) {
        stderr.printf(e.message+"\n");
      }
      break;
  }
  return false;
}
// get the 'requested path' portion of a line
string get_request(string line) {
  string[] parts line.split(" ");
  return parts[1];


public static void main() {
  //we need a GLib mainloop to keep this app running
  MainLoop loop new MainLoop();
  //make the threaded socket service with hella possible threads
  ThreadedSocketService tss new ThreadedSocketService(150);
  //create an IPV4 InetAddress bound to no specific IP address
  InetAddress ia new InetAddress.any(SocketFamily.IPV4);
  //create a socket address based on the netadress and set the port
  InetSocketAddress isa new InetSocketAddress(iaPORT);
  //try to add the addess to the ThreadedSocketService
  try {
    tss.add_address(isaSocketType.STREAMSocketProtocol.TCPnullnull);
  catch(Error e) {
    stderr.printf(e.message+"\n");
    return;
  }
  //connect the 'run' signal that is emitted when there is a connection
  tss.run.connectconnection_handler );
  //start listening 
  stdout.printf(@"Serving on port $PORT\n");
  tss.start();
  //run the main loop
  loop.run();
}

Save the code as "server.vala" and compile with:

valac --pkg gio-2.0 server.vala

run the 'server' and point your browser to http://localhost:7777

If you don't see anything in your browser, there are a few possible reasons.

  1. Your browser is old and needs to be updated
  2. For some reason, you are using Internet Explorer (ouch! why do you hate the internet?)

What's Happening?

When the browser requests http://localhost:7777, a very basic webpage is returned, and the webpage contains a small bit of javascript that creates a new EventSource bound to http://localhost:7777/sse and updates a div on the page with date pushed to the EventSource.

Now I just need to strip the old libsoup code out of my project and add a nice socket based web server of my own creation. [enter evil laugh here] MUAHAHAHA

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
4 plus 8
Answer:
required
2010-07-16

Quite a few of my coding projects have a large text field where the user is expected to do a lot of typing. Actually, it would appear that none of my projects have a large text field. I suppose I should make markdowner and scripsi official jezra.net projects. Anyway, both of the previously mentioned apps have a large text field where the user types and types and types( hey, they are text editing programs ).

  • scripsi is a special purpose dual paned text editor written in Vala
  • markdowner is a special purpose text editor written in Python

Both applications use the GTK+ toolkit to create the user interface.

The Problem


When a GTK TextView widget is used to create a large text input area, if the user's cursor travels past the viewable area of the TextView, which happens when typing to the bottom of the screen, the cursor is below the visible area and the user will need to stop typing and scroll the TextView until it is visible. If the user were a typing ninja then I suppose they wouldn't care about typing without knowing what text is showing up in the TextView. Well, I am not a typing ninja.

The Solution


There is probably a more elegant solution to the problem, but I couldn't find it so you are stuck with what I figured out, and this is my solution:

When the text changes, get the coordinates of the cursor and scroll to those coordinates. This will ensure that the cursor is always visible to the user. How would you like to see a working example in Vala?

Enter the Vala

/* woohoo */
using Gtk;
public static void mainstring[] args) {
  //initialize gtk
  Gtk.init(ref args);
  //make a window
  Window window new Window();
  //make a textbuffer
  TextBuffer textBuffer new TextBuffer(null);
  //make a textview with our buffer
  TextView textViewnew TextView.with_buffer(textBuffer);
  //let the textview wrap at word or char
  textView.set_wrap_mode(WrapMode.WORD_CHAR);
  //we need to know when the text buffer changes
  textBuffer.changed.connect( ()=>{
    //make a textiterator
    TextIter iter TextIter();
    //what is the cursors x coordinate?
    int textView.virtual_cursor_x;
    //what is the cursors y coordinate?
    int textView.virtual_cursor_y;
    //get the text iter at the cursor's coordinates
    textView.get_iter_at_location(out iter,x,y);
    //scroll to the cursor's text iter
    textView.scroll_to_iter(iter,0,false,0,0);
  });
  //make a scrolledwindow to hold the textviw
  ScrolledWindow scroll new ScrolledWindow (nullnull);
  //set the scrollwindow's scrolling policy
  scroll.set_policy (PolicyType.AUTOMATICPolicyType.AUTOMATIC);
  //add the textview to the scollwindow
  scroll.add (textView);
  //add the scrollwindow to our window
  window.add(scroll);
  //show all the widgets
  window.show_all();
  //run the gtk main loop
  Gtk.main();
}

Save the above code in a file named "cursor.vala" and compile with

valac --pkg=gtk+-2.0 cursor.vala -o cursor

Now I need to port the code to Python and implement the change in markdowner.

Now quit reading, and go fix a problem with a dirty hack.

Comments
2010-07-16 x1101:
Have you considered combining the two apps, and simply having different modes? I know, I know you hate features, but just a thought for the betterment for jezracorp
2010-07-16 jezra:
combining the apps would require rewriting markdowner in Vala (because I would prefer to have the app in Vala) and I'm too lazy to port the markdown library to Vala.
2010-07-25 Windigo:
Just a note - raw Markdown is getting pushed to the planet through your RSS feed. Not necessarily a bad thing, but it always gives me the feeling I'm watching you write in your underwear. :s

Also, I'm still working on that FOSScon blog post; depending on how things go, you might even see it today!
2010-10-20 jezra:
yea, this is crap and unnecessary when using ScrollWindow.add(child).
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
1 plus 2
Answer:
required
2010-06-11

Plenty of things happen that are almost blog worthy but just don't have the substance for a full post. For the most part, this is what microblogging is for. However, there are somethings that are too big for a posting on identi.ca. So what is a Jez to do?
Why, have a fun filled frolicking Friday feature fest, of course. The idea is simple: aggregate the little things into one post. Booyah!

Jezra.net


Recently, I've make two changes to my blog, only one of which you the viewer will actually experience.

  1. the blog code has been updated to allow me to write using the Markdown format. I'm actually using PHP Markdown written by Michel Fortin. From a visitor's standpoint, this doesn't really do much, but it certainly makes it easier for me to compose a new posting.

  2. images in blog postings will now be utilizing lightbox, a javascript library for presenting "click to view larger image" images in a pleasant way. There is actually a lightbox2 that I should be using, but I'm happy with the way things are working right now.

Code


heybuddy, the Python identi.ca client, has a new stable release: 0.0.7 "Glens Falls". The biggest feature of this release is probably the inclusion of avatars. Go get it!

In December of 2009, there was a thread in the Linux Outlaws forum about coding a "crap alert" in various programming languages. I wrote the alert in Python and then ported the code to Vala. Recently I cleaned up the Vala code, compiled the code to run on my N810 and made an application package for Maemo. Hopefully the compiled app will also run on an N900, but I don't have one to test on. hint hint

Here is the Vala code:

/* compile using
valac --pkg hildon-1 --pkg gstreamer-0.10 --pkg gtk+-2.0 crapalert.vala -o crapalert

* or 

valac -D TESTBUILD --pkg hildon-1 --pkg gstreamer-0.10 --pkg gtk+-2.0 crapalert.vala -o crapalert

*/

using Gtk;
using Gdk;
using Gst;
using Hildon;
public class CrapAlert:Hildon.Program 
{
    bool is_playing;
    Element playbin;
  bool is_fullscreen;
  Hildon.Window window;
    construct{
        
    }
    public CrapAlert()
    {
            #if TESTBUILD
            string resources Path.build_filename(Environment.get_current_dir(),"Resources",null);
            #else
            string resources Path.build_filename("/","usr","share","crapalert");
            #endif
        
        /* set some variables for later use */
        is_playing false;
        is_fullscreen=false;
        //where is the image for the app?
        string image_src Path.build_filename(resources,"crapalert.png",null);
        string icon_src Path.build_filename(resources,"icon.png",null);
        stdout.printf("image_scr: %s\n",image_src);
        //where is the audio file?
        string audio_src Path.build_filename(resources,"crapalert.wav",null);
        stdout.printf("audio_scr: %s\n",audio_src);
        /* create a gstreamer playbin */
        playbin Gst.ElementFactory.make("playbin""player");
        //set the uri of the playbin to the audio file source
        playbin.set_property("uri""file://" audio_src);
        //get the bus of the playbin
        var bus playbin.get_bus();
        //tell the bus to watch for signals from the playbin
        bus.add_signal_watch();
        //connect "message" signals to a function
        bus.message.connectplayer_message );

        /* build the interface */
        // create a new window
        window new Hildon.Window();
        // connect the delete_event so we can quit the app
        window.destroy.connect( (w)=> { 
            quit(); 
            } );
        window.key_press_event.connect( (w,e)=> {
          check_event(e);
        } );
        // we need a button box to hold the image
        Gtk.Button button new Gtk.Button();
        
        //connect the button to the play_audio function
        button.clicked.connect( (w)=>{ 
            play_audio(); 
            }  );
        //create an image from the image_src
        Gtk.Image image new Gtk.Image.from_file(image_src);
        //add the image to the button

        button.set_image(image);
        //add the button to the window
        window.add(button);
        //set the window icon
        window.set_icon_from_fileicon_src )
        //show the app
        window.show_all();
    }

    public void run()
    {
        //start gtk
        Gtk.main();
    }
    
    private void quit()
    {
        playbin.set_state(Gst.State.NULL);
        Gtk.main_quit();
    }
    
    public void play_audio()
    {
        //are we already playing?
        if(!is_playing)
        {
            is_playing true;
            //make the playbin play
            playbin.set_state(Gst.State.PLAYING);
        }
    }
    
    private void player_message(Message message)
    {
        // check for the end of stream message from the playbin
        var message.type;
        if (== Gst.MessageType.EOS )
        {
            playbin.set_state(Gst.State.NULL);
            is_playing=false;
        }
    }
  private void check_event(EventKey event) {
    if (event.keyval==65475) {
      toggle_fullscreen();
    }
  }
  private void toggle_fullscreen() {
    if (is_fullscreen) {
      window.unfullscreen();
      is_fullscreen=false;
    }else{
      window.fullscreen();
      is_fullscreen=true;
    }
  }
}

public static void main(string[] args)
{
    //init gtk and gstreamer
    Gtk.init(ref args);
    Gst.init(ref args);
    //make a new instance of the crapalert
    CrapAlert ca new CrapAlert();
    //run the crap alert
    ca.run();
}

Here is the crapalert running on my N810. If anyone is interested in the installable binary, it is available at http://www.jezra.net/downloads/crapalert-0.2.deb.


Technology


About two weeks ago, my telephone answering machine died. To be honest, it had been one cassette in the grave for a very long time. Anyway, a short trip to a local thrift store and $6 later; I had taken the leap for the 80s right into the 90s with a digital answering machine/cordless phone combination. That's right, a push button phone. How tech is that?

A few days after connecting my new phone, I received an invitation to Google Voice and I thought I should try out the service. I will be doing a proper review in a month or two when I have had time to properly use Google Voice.

Speaking of speaking, I've been trying to set up a Mumble server and I could use some help testing the machine with someone far away. If you can install Mumble v1.2.2, have a microphone, and live more than 4000 miles from the San Francisco Bay Area, contact me. Actually, if you can install Mumble 1.2.2 then you should contact me.

Now quit reading, and go do something.

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
4 plus 7
Answer:
required
2010-04-01
Since not everyone want to read a bunch of pointless April fools crap postings on the internets, and just in time to probably fail to compile with the latest version of Vala, I have a new little project written in Vala!

Quite a while ago, I made a basic metronome application and named it hubcap in honor of Linux Outlaws host Dan Lynch. Anyone that has ever heard me play music knows just how much I really need a metronome, but that is beyond the point, and I promise that this won't be some pointless April 1st crap.

Fortunately, the Linux Outlaws has two hosts and I had a yearning to hack code and process data.

Say hello to Shnerkel!
Shnerkel is an aggregator and player of the Linux Outlaws ogg feed. Sauce: shnerkel.tar.gz


--Requirments--
gstreamer-0.10
gtk+-2.0
webkit-1.0
libxml-2.0
Vala ( for compiling )

The main impetus for creating this application was to play with webkit in Vala. As I see it, there are a few bonus results of creating this app.

1. Since the app plays the ogg version of the Linux Outlaws audcast, the statics for numbers of downloads of the mp3 and ogg versions will hopefully tip towards ogg.

2. It is a fairly easy way to increase the expose of Linux Outlaws and Ogg, although I'm probably preaching to the choir on both accounts.

3. Almost a full dozen people will have something real to read on April 1st.

The Good
With shnerkel, there is no more waiting to download the audcast. The audio file is streamed over HTTP by the gstreamer library. Shnerkel uses the same audio player class as sap, which really cut down on development time. Thanks Open Source.

The Bad
You will notice the lack of a progress bar. For some reason gstreamer doesn't return the duration of an ogg file being played over HTTP. What the hell is up with that? It is either a problem with Gstreamer or a problem with the Ogg format.

The Ugly
plenty. Before you complain, go look in the mirror. Oh snap! You got burned by that one! In the appwindow.vala file, I pull information from a GTK TreeStore as follows:
string description=""; string file=""; tree_selection = tv.get_selection(); tree_selection.get_selected(out model, out iter); episodeTreeStore.get(iter,3,&description,2,&file,-1);
It seems to me that passing 'description' and 'file' to the function as references is rather un-vala like and the function should instead use 'out' to pass data to the strings. Oh well...

It's almost midnight, but I don't think I'll stay up and write the first page of my movie script for the ScriptFrenzy challenge.


Now quit reading, and go find the elusive Dirk Shnerkelberger.
Comments
2010-04-05 x1101:
Jezra, do you have any plans to package this into an RPM? I would love to include this in BrownHat. (we would also welcome any of your other software, it is for the Outlaws by the Outlaws afterall)
2010-04-05 jezra:
No, I will not be making an RPM. What I will do, is put the code on launchpad and improve the build system so that it will be easier for others to make distro specific packages.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
4 plus 8
Answer:
required
2010-01-21
Sometimes, some things sort of get stuck in my head. The most recent concept to get stuck in my head was the number of elements in immediate proximity to a specific element in a grid. It started with two implementations of the Game of Life, one written in Vala using SDL and one written in Python using Clutter.

Do you know what else deals with the relationship of elements in a grid? MineSweeper!

MineSweeper happens to be one of my favorite computer games. At this point, it should be noted that there are few implementations of minesweeper for Linux. Of the two versions that I found, one requires Gnome and one Required KDE. Seriously? Why does a simple game need to require Gnome or KDE libs? Oh well, I guess I'll just have to write my own.

Enter the Vala:
/* Copyright 2010 jezra lickter licensed GPL version 3 http://www.gnu.org/licenses/gpl-3.0.html compile with: valac --pkg gtk+-2.0 main.vala -o sweeper */ using Gtk; using Gdk; public class Cell:EventBox  {     signal void flip_cell();     signal void flag_cell();     public int row;     public int col;     private Button button;     private Label label;     const string ONE"blue";     const string TWO"dark green";     const string THREE"red";     const string FOUR"purple";     const string FIVE"orange";     const string SIX"yellow";     private Color red;     construct     {         button new Button();         //box = new Box();     }     public Cell(int rint c)     {         Color.parse("red",out red);         row=r;         col=c;         //create the cell         button.set_relief(ReliefStyle.HALF);         button.event.connect( (obj,event)=> { parse_event(event); } );         //box.pack_start(button, true, true, 0);         add(button);         label=new Label(null);         set_size_request(30,30);         //add(box);     }     public bool parse_event(Gdk.Event event)     {                  if(event.button.type == Gdk.EventType.BUTTON_PRESS)         {             if (event.button.button==1// && event.button.button<=3)             {                 flip_cell();             }else ifevent.button.button==)             {                 flag_cell();              }             return true;         }else{             return false;         }     }     public void flag()     {         button.set_label("*");     }     public void end_flag()     {         if(button.get_label()!="*")         {             show_label("*");         }     }          public void show_label(string str)     {         string color;         switch(str)         {             case "1":                 color ONE;                 break;             case "2":                 color TWO;                 break;               case "3":                 color THREE;                 break;             case "4":                 color FOUR;                 break;             case "5":                 color FIVE;                 break;             case "6":                 color SIX;                 break;             /*case "7":                 color = SEVEN;                 break; */             default:                 color "black";                 break;         }         label.set_markup("<span foreground=\"%s\">%s</span>".printf(color,str)      );         remove(button);         add(label);         label.show();     }     public void show_baddy()     {         modify_bg(StateType.NORMALred );         label.set_markup("<span background=\"red\" font_weight=\"heavy\">*</span>");         remove(button);         add(label);         //box.pack_start(label,true,true,0);         label.show();     }          public void disable()     {         button.set_state(Gtk.StateType.INSENSITIVE);     } } public class Application:Gtk.Window {     MainLoop loop;     bool[,] mineArray;     bool[,] flippedArray;     private Button startButton;     private ComboBox levelCombo;     private Table cellTable;     private int rows {get;set;}     private int cols {get;set;}     private int mines;     private Cell[,] cells;     private VBox vbox;     private bool game_playing;     private bool game_needs_new_table;     //define the small/medium/large values     const int small_rows=6;     const int small_cols=10;     const int small_mines=10;     const int medium_rows=10;     const int medium_cols=16;     const int medium_mines=26;     const int large_rows=12;     const int large_cols=20;     const int large_mines=40;     private int required_flips;     private int flip_count;     construct     {         type=Gtk.WindowType.TOPLEVEL;         set_resizable(false);         game_playing=false;         game_needs_new_table=false;         //we default to small         rows small_rows;         cols small_cols;         mines small_mines;     }          public Application()     {         destroy.connect(quit);         vbox new VBox(false,0);         HBox hbox new HBox(false,0);         startButton new Button.with_label("Start");         levelCombo new ComboBox.text();         levelCombo.append_text("Small");         levelCombo.append_text("Medium");         levelCombo.append_text("Large");         //levelCombo.append_text("Custom");         levelCombo.set_active(0);         levelCombo.changed.connectlevel_changed );                  Label dif new Label("Size:");         startButton.clicked.connectstart_game );         hbox.pack_start(startButton,false,false,0);         hbox.pack_start(dif,false,false,0);         hbox.pack_start(levelCombo,false,false,0);         hbox.set_spacing(5);         vbox.pack_start(hbox,false,false,0);         add(vbox);         show_all();         //create the first table         new_table();         cellTable.set_sensitive(false);     }          private void level_changed()     {         string val levelCombo.get_active_text();         switch(val)         {             case "Small":                 rows small_rows;                 cols small_cols;                 mines small_mines;                 break;             case "Medium":                 rows medium_rows;                 cols medium_cols;                 mines medium_mines;                 break;             case "Large":                 rows large_rows;                 cols large_cols;                 mines large_mines;                 break;         }         game_needs_new_table=true;         start_game();         //we should probably resize the window     }          public void start_game()     {         if(game_needs_new_table)         {             new_table();         }         game_playing=true;         //disable the start button         startButton.set_sensitive(false);         cellTable.set_sensitive(true);     }     public void end_game()     {         //disable unflipped cells         for(int r=0r<rowsr++)         {             for(int c=0c<colsc++)             {                 if(!flippedArray[r,c])                 {                     //if this is a mine, mark it as such                     if(mineArray[r,c])                     {                         cells[r,c].end_flag();                     }                     cells[r,c].disable();                 }             }         }         game_needs_new_table=true;         //enable the start button         game_playing false;         startButton.set_sensitive(true);     }          public void new_table()     {         vbox.remove(cellTable);         cellTable make_table();         vbox.pack_startcellTable,true,true,);         cellTable.show_all();     }          private Table make_table()     {         //reset the required flips         required_flips rows*cols mines;         //reset the flip_count         flip_count=0;         mineArray new bool[rows,cols];         flippedArray new bool[rows,cols];         cells new Cell[rows,cols];         Table table new Table(rows,cols,true);         forint r=0;r<rows;r++ )         {             for int c=0c<colsc++ )             {                 flippedArray[r,c]=false;                 mineArray[r,c]=false;                 cells[r,c] = new Cell(r,c);                 cells[r,c].flip_cell.connect( (cell)=>{ flip_cell(cell); } );                 cells[r,c].flag_cell.connect( (cell)=>{ flag_cell(cell); } );                 //add the cell to the table lrtb                 table.attach(cells[r,c],c,c+1,r,r+1,AttachOptions.FILL ,AttachOptions.FILL ,0,0);                    }         }            //make the mines         int minecount=0;         int randomrow;         int randomcol;         Rand rand new Rand();         while(minecount<mines)         {             //pick a random row             randomrowrand.int_range(0,rows);             //pick a random col             randomcol rand.int_range(0,cols);             if(!mineArray[randomrow,randomcol])             {                     mineArray[randomrow,randomcol]=true;                     minecount++;             }         }         return table;     }          public void flag_cellCell )     {         c.flag();     }          private void flip_cell(Cell c)     {         //consider this flipped         flippedArray[c.row,c.col]=true;         if(mineArray[c.row,c.col])         {             c.show_baddy();             end_game();         }else{             detect_neighboring_mines(c.row,c.col);             flip_count+=1;             if(flip_count==required_flips)             {                 //we did it!                 end_game();             }         }     }     private void detect_neighboring_mines(int rint c)     {         string label="";         int mine_count 0;         int r_min r-1;         int r_max r+1;         int c_min c-1;         int c_max c+1;         if(r==0)         {             r_min=r;         }         else if (== rows-1)         {             r_max=r;         }         if c==)         {             c_min=c;         }         else if (== cols-1)         {             c_max=c;          }            int tr r_min;         int tc c_min;         while(tr <= r_max)         {             tc c_min;             while(tc <= c_max)             {                 if tr!=|| tc!=)                 {                     if mineArray[tr,tc])                     {                         mine_count+=1;                     }                 }                 tc+=1;             }             tr+=1;         }         if(mine_count>0)         {             label mine_count.to_string();         }else{             expand_from_empty_cell(r,c);         }         cells[r,c].show_labellabel );     }          private void expand_from_empty_cell(int rint c)     {         int r_min r-1;         int r_max r+1;         int c_min c-1;         int c_max c+1;         if(r==0)         {             r_min=r;         }         else if (== rows-1)         {             r_max=r;         }         if c==)         {             c_min=c;         }         else if (== cols-1)         {             c_max=c;          }            int tr r_min;         int tc c_min;         while(tr <= r_max)         {             tc c_min;             while(tc <= c_max)             {                 if tr!=|| tc!=)                 {                     if ( !flippedArray[tr,tc])                     {                         //check this cell                         flip_cell(cells[tr,tc]);                         //detect_neighboring_mines(tr,tc);                     }                 }                 tc+=1;             }             tr+=1;         }     }          public void run()     {         loop new MainLoop();         loop.run();     }     public void quit()     {         loop.quit();     } } public static void mainstring[] args) {     Gtk.init(ref args);     Application app new Application();     app.run(); }

YUCK! That is some ugly code. Normally, when I write some code, I will first write the steps I want the code to take as comments and I will then write the actual code of the app. Obviously, that was not the case this time. The passion took me.

Requirements
GTK
GDK
Vala

Compiling
valac --pkg gtk+-2.0 main.vala -o sweeper


Screenshots




The game is over when all of the non-mine squares have been selected. Yes, I know that there is no timer, or flag counter and honestly, I don't care; I just want to play.

Now stop reading, and go sweep something.
Comments
2010-01-21 Julian Aloofi:
Your blog is slowly becoming one of the most valuable resources for Vala code examples ^^
At least for newbies like me. Thanks for sharing the code!
2010-01-21 jezra:
I wasn't aware that you are delving into the world of Vala. Good for you! How has the experience been so far?
2010-01-22 Julian Aloofi:
I was just messing around with it a bit, but it looks like it's developing really well.
I really like the language, but currently I'm more focussed on others. Vala still is in an early stage I think, and breaks source compability too often for me :(
2010-02-16 fadereu:
Also, check out the game of GO.
2011-07-10 ray:
Very useful code example. Perhaps the compiler is a little tighter now, but needed to make flip_cell() and flag_cell() public, then add "return" before parse_event() in Cell constructor, to get it to compile. It works, but get a container_remove assertion failed. Finally, had problems with lack of CRs, when copying your code. However, I am very grateful to you, for the example.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
0 plus 1
Answer:
required
2009-12-29
Once again, Conway's Game of Life. This time written in Vala using SDL for the graphics.

In comparison to my previous Game of Life implementation, this version starts faster; much much faster. In the time it takes the Python/Clutter version to make 100 cells, the Vala/SDL version creates 100000 cells. I attribute the speed increase on the following factors:
  • Vala code is compiled to machine code, while Python code needs to be executed by the Python runtime.
  • SDL is a much lower level library than Clutter.
  • Since Clutter utilizes OpenGL for graphics, my graphics card, which is rather old, my not be the best OpenGL renderer

To test the code, save the following as game_of_life.vala
/*compile with valac --pkg sdl game_of_life.vala -o game_of_life */ using SDL; public class Cell {     public Rect on_screen_rect;     public Rect self_rect;     public Surface surface;     public weak Screen screen;     public bool alive {get;set;}     public bool updated;     public int16 size;     private uint32 life_color;     private uint32 dead_color;     public Cell(Screen suint32 flags,int16 xint16 yint16 size,uint32 lcolor,uint32 bg_color )              life_color lcolor;         dead_color bg_color;         updated=false;         screen s;         on_screen_rect.size;         on_screen_rect.size;         on_screen_rect.x;         on_screen_rect.y;         self_rect.size;         self_rect.size;         self_rect.x=0;         self_rect.y=0;         this.size = (int16)size;         alive=false;         //create the surface          surface new Surface.RGB(flagssizesize320,0,0255);        }     public void set_life(bool life)     {         alive=life;         if (alive)         {             surface.fill(null,life_color);         }else{             surface.fill(null,dead_color);         }     }     public void draw()     {         surface.blit(self_rect,screen,on_screen_rect);     } } public class Game {     private const int SCREEN_WIDTH 800;     private const int SCREEN_HEIGHT 600;     private const int SCREEN_BPP 32;     private const int DELAY 100;     private weak SDL.Screen screen;     private bool do_loop;     private Cell[,] cells;     private int16 num_cols;     private int16 num_rows;     private uint32 bg_color;     private int random_max;          public Game(int16 cell_sizeint random_max)     {         this.random_max random_max;         //initialize the video         uint32 surface_flags SurfaceFlag.DOUBLEBUF |                             SurfaceFlag.HWACCEL                            SurfaceFlag.HWSURFACE;         screen Screen.set_video_mode (SCREEN_WIDTHSCREEN_HEIGHT,                                         SCREEN_BPPsurface_flags);         if (screen == null) {             GLib.error ("Could not set video mode.");         }         //we have a screen, define some colors         bg_color=screen.format.map_rgb(0,0,0);         uint32 life_color=screen.format.map_rgb(0,255,0);         SDL.WindowManager.set_caption ("Game of Life: Vala, SDL""");         //make all of the cells         //'''fill the screen with rectangles'''         num_cols = (int16)SCREEN_WIDTH/cell_size;         num_rows = (int16)SCREEN_HEIGHT/cell_size;         uint32 cells_to_create num_cols*num_rows;         int16 y;         int16 x;         cells new Cell[num_rows,num_cols];         stdout.printf"%0.f cells to create\n",cells_to_create);         for int16 row=0row<num_rowsrow++)         {             row*cell_size;             for int16 column=0column<num_colscolumn++)             {                 x=column*cell_size;                 //set the column index to a rect that is cell_size by cell_size                 cells[row,column] = new Cell(screen,surface_flags,x,y,cell_size,life_color,bg_color);             }             cells_to_create-=num_cols;         }         stdout.printf"cells created\n" );         do_loop true;     }          private void seed_life(bool reseed=false)     {         Rand rand new Rand();         stdout.printf"adding random life\n" );         //loop through the rows         for(int r=0r<num_rows;r++)         {             //loop through the columns             for(int c=0c<num_cols c++)             {                 //generate a number                 int rand.int_range(0,random_max);                 if (i==0)                 {                     //r,c is alive                     cells[r,c].set_life(true);                 }else if (reseed)                 {                     cells[r,c].set_life(false);                 }             }         }     }          public void run()     {         //seed life into the cells         seed_life();         //do stuff         while (do_loop) {             this.draw ();             this.process_events ();             detect_life();             SDL.Timer.delay (DELAY);         }     }     private void quit()     {         do_loop false;     }     private void process_events()     {         Event event Event ();         while (Event.poll (event) == 1) {             switch (event.type) {             case EventType.QUIT:                 quit();                 break;             case EventType.KEYDOWN:                 this.on_keyboard_event (event.key);                 break;             }         }     }           private void on_keyboard_event (KeyboardEvent event)      {         if(event.keysym.sym==KeySymbol.q)         {             quit();         }         if(event.keysym.sym==KeySymbol.r)         {             seed_life(true);         }     }          private void draw()     {         screen.fill (nullbg_color);         foreachCell in cells )         {             c.draw();         }         screen.update_rect(0,0,screen.w,screen.h);     }          private int living_neighbors(int rint c)     {         int alive 0;         int r_min r-1;         int r_max r+1;         int c_min c-1;         int c_max c+1;         if(r==0)         {             r_min=r;         }         else if (== num_rows-1)         {             r_max=r;         }         if c==)         {             c_min=c;         }         else if (== num_cols-1)         {             c_max=c;          }            int tr r_min;         int tc c_min;         while(tr <= r_max)         {             tc c_min;             while(tc <= c_max)             {                 if tr!=|| tc!=)                 {                     if cells[tr,tc].alive)                     {                         alive+=1;                     }                 }                 tc+=1;             }             tr+=1;         }         return alive;     }          public void detect_life()     {         int r;         int c;         int index;         Array<int>? dead_array new Array<int>(false,false,(uint)sizeof(int));         Array<int>? alive_array new Array<int>(false,false,(uint)sizeof(int));         bool alive;         int ln;         for(r=0r<num_rowsr++)         {             for(c=0c<num_colsc++)             {                 alive cells[r,c].alive;                 ln living_neighbors(r,c);                 ifalive && ( ln<|| ln>))                 {                     //this cell will die; boohoo                     dead_array.append_val(r);                     dead_array.append_val(c);                 }                 else if(!alive && ln==3)                 {                     //this cell is now alive; damn zombies                     alive_array.append_val(r);                     alive_array.append_val(c);                 }             }         }         //kill what needs killing          uint final_index dead_array.length-1;         for(index=index<final_index index+=2)         {             dead_array.index(index);             cdead_array.index(index+1);             cells[r,c].set_life(false);         }         //frankenstein what needs life         final_index alive_array.length-1;         for(index=index<final_index index+=2)         {             alive_array.index(index);             calive_array.index(index+1);             cells[r,c].set_life(true);         }     }    } public static void main(string[] args) {     int16 cell_size 2;     int random_max 15;     SDL.init (InitFlag.VIDEO);     Game game new Game(cell_size,random_max);     game.run();     SDL.quit(); }
or you can just download game_of_life.vala
The code is compiled with
valac --pkg sdl game_of_life.vala -o game_of_life

As in the Python/Clutter example, pressing "q" will quit the game, and pressing "r" will reset the game.

Now stop reading, and get mesmerized by little green blocks.
Comments
2010-04-17 Yuvi:
Copy pasting the code messes up the formatting big time :(

I'd mucho appreciate it if you put it up in a way where we can get a raw version out easily.

:)
2010-04-17 jezra:
What text editor are you pasting the code into?
2010-04-20 (((1/f))):
Hi, I have the same problem as Yuvi. I have two editors on Puppy Linux 431 namely Geany and NicoEdit and they won't accept the copypasta.
2010-04-21 jezra:
I have added a link, after the code, to a source file containing the code.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
1 minus 0
Answer:
required
2009-06-01
Having recently created a custom terminal app to fit my usage needs, I quickly became aware of two issues with the terminal application that really bothered me.

1. when running screen, backspace didn't work. Since I typically have a screen session running when I'm using a terminal, this was a high priority problem. Fortunately, a bit of research in the VTE documentation turned up the class function "set_backspace_binding", which sets what type of input is sent when a user presses the backspace key. I opted for sending an ASCII Backspace character.

2. Being able to copy and paste from a terminal window is a feature that I have become accustomed to, yet muterm was definitely lacking. Easy enough, check key-presses and if the user has pressed "control+shift+c", copy the selected text in the terminal to the clipboard. Do I really need to mention what "control+shift+v" does? OK, it pastes text from the clipboard to the terminal.

Here is the new code with the fixes
using Gtk; using Vte; private class Term {     private Terminal term;     private Term()     {         //start with a gtk window         Window w;         //we also need a vte terminal         //create our new window         new Window(Gtk.WindowType.TOPLEVEL);         //create the new terminal         term new Terminal();         //connect exiting on the terminal with quiting the app         term.child_exited.connect ( (t)=> { Gtk.main_quit(); } );         //connect keypress events from the window         term.key_press_event.connect((w,event) => {this.process_event(event); } );         //fork a command to run when the terminal is started         term.fork_command(null,null,null,nulltruetrue,true);         //set the default terminal backspace binding         term.set_backspace_binding(TerminalEraseBinding.ASCII_BACKSPACE);         //add the terminal to the window         w.add(term);         //maximize the window         w.maximize();         //undecorate the window         w.set_decorated(false);         //try to set the icon         try{             w.set_icon_from_file("/usr/share/pixmaps/gnome-term.png");         }catch(Error er)         {             //we don't really need to print this error             stdout.printf(er.message);         }         //show our window and all children of our window         w.show_all();     }     private bool process_event(Gdk.EventKey event)     {         uint keyval;         uint state;         //we are looking for control+shift+c and control+shift+c         keyval event.keyval;         state event.state;         //print some data         if(keyval>&& state>0)         if(state==5)         {             if(keyval==67)             {                 //this is a copy request                 term.copy_clipboard();                 return true;             }else if(keyval==86)             {                 //this is a paste request                 term.paste_clipboard();                 return true;             }         }         return false;     }     private void run()     {         //start the gtk mainloop         Gtk.main();     }     private static void main(string[] args)     {         Term t;         Gtk.init(ref args);         new Term();         t.run();     } }
Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
3 plus 6
Answer:
required
2009-05-19
For a while now, after starting my computer and launching a terminal, I've been maximizing and undecorating the terminal window. Unfortunately, I was unable to find a way to use command line arguments to launch my current favorite terminal: Sakura, in maximized and undecorated mode. To put it simply, I had three options:
  • Try to find another terminal app that does what I need
  • Edit the Sakura soure code to add the maximize and undecorate option
  • Code my own app

I elected for "code my own app" and I called it muterm because it is a (m)aximized (u)ndecorated (term)inal emulator. This would also be a good introduction for anyone interested in adding a terminal emulator to a Vala project.
Here is my main.vala code:
using Gtk; using Vte; private class Term {     private Term()     {         //start with a gtk window         Window w;         //we also need a vte terminal         Terminal term;         //create our new window         new Window(Gtk.WindowType.TOPLEVEL);         //create the new terminal         term new Terminal();         //connect exiting on the terminal with quiting the app         term.child_exited.connect ( (t)=> { Gtk.main_quit(); } );         //fork a command to run when the terminal is started         term.fork_command(null,null,null,nulltruetrue,true);         //add the terminal to the window         w.add(term);         //maximize the window         w.maximize();         //undecorate the window         w.set_decorated(false);         //try to set the icon         try{             w.set_icon_from_file("/usr/share/pixmaps/gnome-term.png");         }catch(Error er)         {             //we don't really need to print this error             stdout.printf(er.message);         }         //show our window and all children of our window         w.show_all();     }     private void run()     {         //start the gtk mainloop         Gtk.main();     }     private static void main(string[] args)     {         Term t;         Gtk.init(ref args);         new Term();         t.run();     } }

The code is compiled with:
valac --pkg gtk+-2.0 --pkg vte main.vala -o muterm

It took me a while to realize that I didn't need to have values for all of the Vte.Terminal.fork_command() function. Howerver, I should allow passing a directory as an arguement and have muterm start in that directory as the `pwd`. Copying and pasting text in the terminal would also be a great idea. Maybe, we'll see what the future holds.

I'd show a screenshot, but really, how different do terminals really look?
Comments
2009-05-25 jezra:
Backspace wasn't being handled correctly when using "screen", so I needed to add the following after creating the new VTE Terminal instance:
term.set_backspace_binding(TerminalEraseBinding.ASCII_BACKSPACE);
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
7 minus 4
Answer:
required
2008-11-03
Recently, I decided to start learning the Vala programming language. As with any programming language, I find that the easiest way to learn is to create a real world program, and I don't mean "hello world". During the start of my Vala development, I became aware of a few issues that took me a while to hunt down solutions for and I thought it would be best to share the info I have collected. Vala, as of this writing, is at version 0.4 and being quite new, the language doesn't have the most in-depth documentation, so I'll use working code examples.

Issue 1: How do I convert an integer into a string in Vala?
Answer: Vala strings are the same as GLib strings - Gstrings, yea, laugh it up. As such one can use the "printf" function of the string object to change an integer to a string. Take the following code as an example:
//nearly all vala apps will use Glib
using GLib;

//create a class that will we will call to print a string
public class Printer {
//create a function that we can use to print a string
public void print_line(string str)
{
stdout.printf("%sn",str);
}
}

//create the main class
private class Main {
//create the entrance to the application
private static void main()
{
//make an instance of the printer
var printer = new Printer();
printer.print_line("hello world");
//loop 5 times
for(int i=0; i<5; i++)
{
// convert the integer value of i to a string
string i_string = "%d".printf(i);
//print the value of i
printer.print_line("i = "+i_string);
}
}
}


OK, did you copy the code and paste into your favorite editor?
Let's look at line 25:
string i_string = "%d".printf(i);

I created a string object named 'i_string' and set it equal to a string object that is print formatting an integer.

Issue #2: How do I list the actual name of the package that I can include when I'm compiling with valac?
Answer: When compile a Vala app that uses libraries like GTK, XML or Webkit, one needs to add '--pkg [packagename]' to the compile arguments used by valac. A list of the available packages can be found by doing the following:
1. on the commandline enter 'which valac' to find the install location of the valac compiler, since my compiler is installed in /usr/local/bin.....
2. on the commandline, I enter 'ls /usr/local/share/vala/vapi' and I will see a list of all of the packages I can use.
Had my valac been installed in /usr/bin, I would search for packages in /usr/share/vala/vapi

Issue #3: OK, this isn't really an issue, but it was a oddity that I had to accept. All Vala apps have an entry point in a function named "main", that is to say, that Vala code starts execution at a function named "main" and this function can create an instance of the class that "main" is a function of. To me this seemed very weird. Oh well, I got over it. Re-writing the above code to show the main function creating an instance of the class that main is a function of results in the following code:
//nearly all vala apps will use Glib
using GLib;
//create the printer class
private class Printer {
//create a function that we can use to print a string
public void print_line(string str)
{
stdout.printf("%sn",str);
}
//create the entrance to the application
private static void main()
{
//make an instance of the printer
var printer = new Printer();
printer.print_line("hello world");
//loop 5 times
for(int i=0; i<5; i++)
{
// convert the integer value of i to a string
string i_string = "%d".printf(i);
//print the value of i
printer.print_line("i = "+i_string);
}
}
}


By the way, I've been happily writing all of my Vala code using valide: a Vala specific IDE.
Comments
2008-11-11 jezra:
Oh geez. I could have used the "to_int()" method of the string class. Example:
string number="1";
int value = number.to_int();
2008-12-29 Justin:
I think you mean that you could have used to_string().

For example, instead of

string i_string = "%d".printf(i);

you could have written

string i_string = i.to_string();

Thank you very much for the post though. Vala has caught my interest.

Justin
2008-12-29 jezra:
oops,
Yes, I should/could have used 'to_string()' to convert the int to a string.
Thanks for the catch.
2009-08-18 Arkadi:
Thank you very much for your post.
I'm learning VALA too and writing a blog my self... Good luck.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
2 plus 8
Answer:
required
subscribe
 
2019
2016
2015
2014
2013
2012
2011
2010
December
November
October
September
August
July
June
May
April
March
February
January
2009
December
November
October
September
August
July
June
May
April
March
February
January
2008