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
* 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<string, string> args;
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(ia, PORT);
//try to add the address to the ThreadedSocketService
try {
tss.add_address(isa, SocketType.STREAM, SocketProtocol.TCP, null, null);
} 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.connect( connection_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_line( out size );
request = get_request( first_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_response( response, dos );
return false;
}
private void serve_response(Response response, DataOutputStream 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]);
}
} catch( Error 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 filepath= Path.build_filename(public_dir, request_path);
Response response = Response();
response.content_type = "text/plain";
response.status_code = StatusCode.ERROR;
//does the file exist?
if (FileUtils.test(filepath, GLib.FileTest.IS_REGULAR) ) {
//serve the file
bool read_failed = true;
uint8[] data = {};
try {
FileUtils.get_data(filepath, out data);
response.data = data;
response.content_type = get_content_type( filepath );
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.match( file, 0, out 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 r = Request();
r.args = new HashTable<string, string>(str_hash, str_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("&");
foreach( string 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
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!
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:
- 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"
- Read "x" number of bytes from the next line. This is a "chunk" of data.
- 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
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 i = 0; i < hexlen ; 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 = 0 ; j < i ; j++) {
multiplier *= 16;
}
ret_val += chr_int * multiplier;
}
return ret_val;
}
public static void main() {
string[] hexvals = {"9AC3","1234","b525","abcdef","bbb"};
int64 i;
foreach( string hexval in hexvals) {
i = 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.
c'est plus concis
<code>
public int hexval(string c)
{
string ref = "0123456789abcdef";
return ref.index_of(c);
}
</code>
I hope there will be a substitute before the deprecated version vanishes.
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 > 1 ){
port = int.parse(hpbits[1]);
}
if(bits.length > 1) {
for (int i=1 ; i<bits.length; i++) {
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_value( string line ){
if(status==null) {
status = line;
var line_bits = status.split(" ");
code = line_bits[1];
} else {
MatchInfo match_info;
string key,val;
if( key_value_regex.match(line,GLib.RegexMatchFlags.ANCHORED, out 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 Downloader( string url, string? local_file=null, int? throttler=0) {
borked = false;
length = 0;
dl_length = 0;
this.throttler = throttler;
download_complete = false;
downloader_finished = false;
this.url = url;
kill_download= false;
if (local_file != null) {
local_file_path = local_file;
} else {
local_file_path = Path.get_basename(url);
}
download( url );
}
public bool is_borked() {
return borked;
}
void download(string remote, int? redirects = 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.host, null);
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.request, pu.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 > 1 );
//check and handle redirects
if(header.code == "301" || header.code=="302") {
//how many redirects is this?
if ( redirects<=0 ) {
stderr.printf("Error: Too many Redirects\n");
} else {
//download from the redirect location
download( header.Location, redirects--);
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_file, local_file_path);
} catch (Error e) {
stderr.printf ("%s\n", e.message);
return ;
}
}
}
public static void main( string[] args) {
string url = "";
string local_file = "";
bool die = false;
Downloader d;
switch( args.length ) {
case 3 :
local_file = args[2];
url = args[1];
d = new Downloader(url, local_file);
break;
case 2 :
url = args[1];
d = 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
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.
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.
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
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
//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_line( out size );
request = get_request( first_line );
} catch (Error e) {
stderr.printf(e.message+"\n");
}
//do stuff based on the request
switch( request ) {
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(ia, PORT);
//try to add the addess to the ThreadedSocketService
try {
tss.add_address(isa, SocketType.STREAM, SocketProtocol.TCP, null, null);
} catch(Error e) {
stderr.printf(e.message+"\n");
return;
}
//connect the 'run' signal that is emitted when there is a connection
tss.run.connect( connection_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:
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.
- Your browser is old and needs to be updated
- 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
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
using Gtk;
public static void main( string[] 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 textView= new 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 x = textView.virtual_cursor_x;
//what is the cursors y coordinate?
int y = 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 (null, null);
//set the scrollwindow's scrolling policy
scroll.set_policy (PolicyType.AUTOMATIC, PolicyType.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
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.
Also, I'm still working on that FOSScon blog post; depending on how things go, you might even see it today!
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.
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.
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:
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.connect( player_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_file( icon_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 t = message.type;
if (t == 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.
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.

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:
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...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'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.
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 r, int 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 if( event.button.button==3 ) { 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.NORMAL, red ); 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.connect( level_changed ); Label dif = new Label("Size:"); startButton.clicked.connect( start_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=0; r<rows; r++) { for(int c=0; c<cols; c++) { 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_start( cellTable,true,true,0 ); 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); for( int r=0;r<rows;r++ ) { for ( int c=0; c<cols; c++ ) { 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 randomrow= rand.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_cell( Cell c ) { 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 r, int 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 (r == rows-1) { r_max=r; } if ( c==0 ) { c_min=c; } else if (c == 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!=r || tc!=c ) { 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_label( label ); } private void expand_from_empty_cell(int r, int 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 (r == rows-1) { r_max=r; } if ( c==0 ) { c_min=c; } else if (c == 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!=r || tc!=c ) { 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 main( string[] 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.
At least for newbies like me. Thanks for sharing the code!
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 :(
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
or you can just download 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 s, uint32 flags,int16 x, int16 y, int16 size,uint32 lcolor,uint32 bg_color ) { life_color = lcolor; dead_color = bg_color; updated=false; screen = s; on_screen_rect.w = size; on_screen_rect.h = size; on_screen_rect.x = x; on_screen_rect.y = y; self_rect.w = size; self_rect.h = size; self_rect.x=0; self_rect.y=0; this.size = (int16)size; alive=false; //create the surface surface = new Surface.RGB(flags, size, size, 32, 0,0,0, 255); } 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_size, int 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_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, surface_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=0; row<num_rows; row++) { y = row*cell_size; for ( int16 column=0; column<num_cols; column++) { 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=0; r<num_rows;r++) { //loop through the columns for(int c=0; c<num_cols ; c++) { //generate a number int i = 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 (null, bg_color); foreach( Cell c in cells ) { c.draw(); } screen.update_rect(0,0,screen.w,screen.h); } private int living_neighbors(int r, int 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 (r == num_rows-1) { r_max=r; } if ( c==0 ) { c_min=c; } else if (c == 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!=r || tc!=c ) { 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=0; r<num_rows; r++) { for(c=0; c<num_cols; c++) { alive = cells[r,c].alive; ln = living_neighbors(r,c); if( alive && ( ln<2 || ln>3 )) { //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=0 ; index<final_index ; index+=2) { r = dead_array.index(index); c= dead_array.index(index+1); cells[r,c].set_life(false); } //frankenstein what needs life final_index = alive_array.length-1; for(index=0 ; index<final_index ; index+=2) { r = alive_array.index(index); c= alive_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(); }
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.
I'd mucho appreciate it if you put it up in a way where we can get a raw version out easily.
:)
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 w = 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,null, true, true,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>0 && 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); t = new Term(); t.run(); } }
- 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 w = 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,null, true, true,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); t = 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?
term.set_backspace_binding(TerminalEraseBinding.ASCII_BACKSPACE);
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:
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.
string number="1";
int value = number.to_int();
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
Yes, I should/could have used 'to_string()' to convert the int to a string.
Thanks for the catch.
I'm learning VALA too and writing a blog my self... Good luck.
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.
Ewww, that sound far to much like I just told you to RTFM. sorry buddy.
:-)