user: message tests

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This needs a lot more work, but it's at a stage where we
can work on it while it's in the tree. To make some real
progress on this front, we need to:
  -- add tests for all the sequences documented here
  -- run the tests on various versions of Windows
  -- fix the darn things

I've tried to preserve all the metadata in the original
document. However, it is not clear what do to with it:
  -- how do we test for the sent/posted business
  -- is it important to know if a message was sent
     by the DefaultWindowProc? If yes, how do we test it?

There are a lot more questions about this one, but I'll
stop here :) Help on finishing this up would be greatly
appreciated.

Please also:
    cvs rm -f documentation/gui

ChangeLog
    Move the message sequences documented in documentation/gui
    into a unit test.

Index: dlls/user/tests/Makefile.in
===================================================================
RCS file: /var/cvs/wine/dlls/user/tests/Makefile.in,v
retrieving revision 1.4
diff -u -r1.4 Makefile.in
--- dlls/user/tests/Makefile.in	26 Apr 2003 02:09:43 -0000	1.4
+++ dlls/user/tests/Makefile.in	28 Sep 2003 16:15:43 -0000
@@ -9,6 +9,7 @@
 	class.c \
 	generated.c \
 	listbox.c \
+	msg.c \
 	sysparams.c \
 	win.c \
 	wsprintf.c
--- /dev/null	2003-01-30 05:24:37.000000000 -0500
+++ dlls/user/tests/msg.c	2003-10-01 01:15:54.000000000 -0400
@@ -0,0 +1,484 @@
+/*
+ * Unit tests for window message handling
+ *
+ * Copyright 1999 Ove Kaaven
+ * Copyright 2003 Dimitrie O. Paun
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "wingdi.h"
+#include "winuser.h"
+
+#include "wine/test.h"
+
+
+/*
+FIXME: add tests for these
+Window Edge Styles (Win31/Win95/98 look), in order of precedence:
+ WS_EX_DLGMODALFRAME: double border, WS_CAPTION allowed
+ WS_THICKFRAME: thick border
+ WS_DLGFRAME: double border, WS_CAPTION not allowed (but possibly shown anyway)
+ WS_BORDER (default for overlapped windows): single black border
+ none (default for child (and popup?) windows): no border
+*/
+
+typedef enum { 
+    sent=0x1, posted=0x2, parent=0x4, wparam=0x8, lparam=0x10,
+    defwinproc=0x20
+} msg_flags_t;
+
+struct message {
+    UINT message;          /* the WM_* code */
+    msg_flags_t flags;     /* message props */
+    WPARAM wParam;         /* expacted value of wParam */
+    LPARAM lParam;         /* expacted value of lParam */
+};
+
+/* CreateWindow (for overlapped window, not initially visible) (16/32) */
+static struct message WmCreateOverlappedSeq[] = {
+    { WM_GETMINMAXINFO, sent },
+    { WM_NCCREATE, sent },
+    { WM_NCCALCSIZE, sent|wparam, 0 },
+    { WM_CREATE, sent },
+    { 0 }
+};
+/* ShowWindow (for overlapped window) (16/32) */
+static struct message WmShowOverlappedSeq[] = {
+    { WM_SHOWWINDOW, sent|wparam, 1 },
+    { WM_WINDOWPOSCHANGING, sent|wparam, /*FIXME: SWP_NOMOVE|SWP_NOSIZE|SWP_SHOWWINDOW*/ 0 },
+    /* FIXME: WM_QUERYNEWPALETTE, if in 256-color mode */
+    { WM_WINDOWPOSCHANGING, sent|wparam, /*FIXME: SWP_NOMOVE|SWP_NOSIZE*/ 0 },
+    { WM_ACTIVATEAPP, sent|wparam, 1 },
+    { WM_NCACTIVATE, sent|wparam, 1 },
+    { WM_GETTEXT, sent|defwinproc },
+    { WM_ACTIVATE, sent|wparam, 1 },
+    { WM_SETFOCUS, sent|wparam|defwinproc, 0 },
+    { WM_NCPAINT, sent|wparam, 1 },
+    { WM_GETTEXT, sent|defwinproc },
+    { WM_ERASEBKGND, sent },
+    { WM_WINDOWPOSCHANGED, sent|wparam, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_SHOWWINDOW },
+    { WM_SIZE, sent },
+    { WM_MOVE, sent },
+    { 0 }
+};
+
+/* DestroyWindow (for overlapped window) (32) */
+static struct message WmDestroyOverlappedSeq[] = {
+    { WM_WINDOWPOSCHANGING, sent|wparam, 0 },
+    { WM_WINDOWPOSCHANGED, sent|wparam, 0 },
+    { WM_NCACTIVATE, sent|wparam, 0 },
+    { WM_ACTIVATE, sent|wparam, 0 },
+    { WM_ACTIVATEAPP, sent|wparam, 0 },
+    { WM_KILLFOCUS, sent|wparam, 0 },
+    { WM_DESTROY, sent },
+    { WM_NCDESTROY, sent },
+    { 0 }
+};
+/* CreateWindow (for child window, not initially visible) */
+static struct message WmCreateChildSeq[] = {
+    { WM_NCCREATE, sent }, 
+    /* child is inserted into parent's child list after WM_NCCREATE returns */
+    { WM_NCCALCSIZE, sent|wparam, 0 },
+    { WM_CREATE, sent },
+    { WM_SIZE, sent },
+    { WM_MOVE, sent },
+    { WM_PARENTNOTIFY, sent|parent|wparam, 1 },
+    { 0 }
+};
+/* ShowWindow (for child window) */
+static struct message WmShowChildSeq[] = {
+    { WM_SHOWWINDOW, sent|wparam, 1 },
+    { WM_WINDOWPOSCHANGING, sent|wparam, 0 },
+    { WM_ERASEBKGND, sent|parent },
+    { WM_WINDOWPOSCHANGED, sent|wparam, 0 },
+    { 0 }
+};
+/* DestroyWindow (for child window) */
+static struct message WmDestroyChildSeq[] = {
+    { WM_PARENTNOTIFY, sent|parent|wparam, 2 },
+    { WM_SHOWWINDOW, sent|wparam, 0 },
+    { WM_WINDOWPOSCHANGING, sent|wparam, 0 },
+    { WM_ERASEBKGND, sent|parent },
+    { WM_WINDOWPOSCHANGED, sent|wparam, 0 },
+    { WM_DESTROY, sent },
+    { WM_NCDESTROY, sent },
+    { 0 }
+};
+/* Moving the mouse in nonclient area */
+static struct message WmMouseMoveInNonClientAreaSeq[] = { /* FIXME: add */
+    { WM_NCHITTEST, sent },
+    { WM_SETCURSOR, sent },
+    { WM_NCMOUSEMOVE, posted },
+    { 0 }
+};
+/* Moving the mouse in client area */
+static struct message WmMouseMoveInClientAreaSeq[] = { /* FIXME: add */
+    { WM_NCHITTEST, sent },
+    { WM_SETCURSOR, sent },
+    { WM_MOUSEMOVE, posted },
+    { 0 }
+};
+/* Moving by dragging the title bar (after WM_NCHITTEST and WM_SETCURSOR) (outline move) */
+static struct message WmDragTitleBarSeq[] = { /* FIXME: add */
+    { WM_NCLBUTTONDOWN, sent|wparam, HTCAPTION },
+    { WM_SYSCOMMAND, sent|defwinproc|wparam, SC_MOVE+2 },
+    { WM_GETMINMAXINFO, sent|defwinproc },
+    { WM_ENTERSIZEMOVE, sent|defwinproc },
+    { WM_WINDOWPOSCHANGING, sent|defwinproc },
+    { WM_WINDOWPOSCHANGED, sent|defwinproc },
+    { WM_MOVE, sent|defwinproc },
+    { WM_EXITSIZEMOVE, sent|defwinproc },
+    { 0 }
+};
+/* Sizing by dragging the thick borders (after WM_NCHITTEST and WM_SETCURSOR) (outline move) */
+static struct message WmDragThinkBordersBarSeq[] = { /* FIXME: add */
+    { WM_NCLBUTTONDOWN, sent|wparam, 0xd },
+    { WM_SYSCOMMAND, sent|defwinproc|wparam, 0xf004 },
+    { WM_GETMINMAXINFO, sent|defwinproc },
+    { WM_ENTERSIZEMOVE, sent|defwinproc },
+    { WM_SIZING, sent|defwinproc|wparam, 4}, /* one for each mouse movement */
+    { WM_WINDOWPOSCHANGING, sent|defwinproc },
+    { WM_GETMINMAXINFO, sent|defwinproc },
+    { WM_NCCALCSIZE, sent|defwinproc|wparam, 1 },
+    { WM_NCPAINT, sent|defwinproc|wparam, 1 },
+    { WM_GETTEXT, sent|defwinproc },
+    { WM_ERASEBKGND, sent|defwinproc },
+    { WM_WINDOWPOSCHANGED, sent|defwinproc },
+    { WM_MOVE, sent|defwinproc },
+    { WM_SIZE, sent|defwinproc },
+    { WM_EXITSIZEMOVE, sent|defwinproc },
+    { 0 }
+};
+/* Resizing child window with MoveWindow (32) */
+static struct message WmResizingChildWithMoveWindowSeq[] = {
+    { WM_WINDOWPOSCHANGING, sent },
+    { WM_NCCALCSIZE, sent|wparam, 1 },
+    { WM_ERASEBKGND, sent },
+    { WM_WINDOWPOSCHANGED, sent },
+    { WM_MOVE, sent|defwinproc },
+    { WM_SIZE, sent|defwinproc },
+    { 0 }
+};
+/* Clicking on inactive button */
+static struct message WmClickInactiveButtonSeq[] = { /* FIXME: add */
+    { WM_NCHITTEST, sent },
+    { WM_PARENTNOTIFY, sent|parent|wparam, WM_LBUTTONDOWN },
+    { WM_MOUSEACTIVATE, sent },
+    { WM_MOUSEACTIVATE, sent|parent|defwinproc },
+    { WM_SETCURSOR, sent },
+    { WM_SETCURSOR, sent|parent|defwinproc },
+    { WM_LBUTTONDOWN, posted },
+    { WM_KILLFOCUS, posted|parent },
+    { WM_SETFOCUS, posted },
+    { WM_CTLCOLORBTN, posted|parent },
+    { BM_SETSTATE, posted },
+    { WM_CTLCOLORBTN, posted|parent },
+    { WM_LBUTTONUP, posted },
+    { BM_SETSTATE, posted },
+    { WM_CTLCOLORBTN, posted|parent },
+    { WM_COMMAND, posted|parent },
+    { 0 }
+};
+/* Reparenting a button (16/32) */
+/* The last child (button) reparented gets topmost for its new parent. */
+static struct message WmReparentButtonSeq[] = { /* FIXME: add */
+    { WM_SHOWWINDOW, sent|wparam, 0 },
+    { WM_WINDOWPOSCHANGING, sent|wparam, SWP_HIDEWINDOW|SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER },
+    { WM_ERASEBKGND, sent|parent },
+    { WM_WINDOWPOSCHANGED, sent|wparam, SWP_HIDEWINDOW|SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER },
+    { WM_WINDOWPOSCHANGING, sent|wparam, SWP_NOSIZE|SWP_NOZORDER },
+    { WM_CHILDACTIVATE, sent },
+    { WM_WINDOWPOSCHANGED, sent|wparam, SWP_NOSIZE|SWP_NOREDRAW|SWP_NOZORDER },
+    { WM_MOVE, sent|defwinproc },
+    { WM_SHOWWINDOW, sent|wparam, 1 },
+    { 0 }
+};
+/* Creation of a modal dialog (32) */
+static struct message WmCreateModalDialogSeq[] = { /* FIXME: add */
+    { WM_CANCELMODE, sent|parent },
+    { WM_KILLFOCUS, sent|parent },
+    { WM_ENABLE, sent|parent|wparam, 0 },
+    /* (window proc creation messages not tracked yet, because...) */
+    { WM_SETFONT, sent },
+    { WM_INITDIALOG, sent },
+    /* (...the window proc message hook was installed here, IsVisible still FALSE) */
+    { WM_NCACTIVATE, sent|parent|wparam, 0 },
+    { WM_GETTEXT, sent|defwinproc },
+    { WM_ACTIVATE, sent|parent|wparam, 0 },
+    { WM_WINDOWPOSCHANGING, sent },
+    { WM_WINDOWPOSCHANGING, sent|parent },
+    { WM_NCACTIVATE, sent|wparam, 1 },
+    { WM_ACTIVATE, sent|wparam, 1 },
+    /* (setting focus) */
+    { WM_SHOWWINDOW, sent|wparam, 1 },
+    { WM_WINDOWPOSCHANGING, sent },
+    { WM_NCPAINT, sent },
+    { WM_GETTEXT, sent|defwinproc },
+    { WM_ERASEBKGND, sent },
+    { WM_CTLCOLORDLG, sent|defwinproc },
+    { WM_WINDOWPOSCHANGED, sent },
+    { WM_PAINT, sent },
+    /* FIXME: (bunch of WM_CTLCOLOR* for each control) */
+    { WM_PAINT, sent|parent },
+    { WM_ENTERIDLE, sent|parent|wparam, 0},
+    { WM_SETCURSOR, sent|parent },
+    { 0 }
+};
+/* Destruction of a modal dialog (32) */
+static struct message WmDestroyModalDialogSeq[] = { /* FIXME: add */
+    /* (inside dialog proc: EndDialog is called) */
+    { WM_ENABLE, sent|parent|wparam, 1 },
+    { WM_SETFOCUS, sent },
+    { WM_WINDOWPOSCHANGING, sent },
+    { WM_NCPAINT, sent|parent },
+    { WM_GETTEXT, sent|defwinproc },
+    { WM_ERASEBKGND, sent|parent },
+    { WM_WINDOWPOSCHANGED, sent },
+    { WM_NCACTIVATE, sent|wparam, 0 },
+    { WM_ACTIVATE, sent|wparam, 0 },
+    { WM_WINDOWPOSCHANGING, sent },
+    { WM_WINDOWPOSCHANGING, sent|parent },
+    { WM_NCACTIVATE, sent|parent|wparam, 1 },
+    { WM_GETTEXT, sent|defwinproc },
+    { WM_ACTIVATE, sent|parent|wparam, 1 },
+    { WM_KILLFOCUS, sent },
+    { WM_SETFOCUS, sent|parent },
+    { WM_DESTROY, sent },
+    { WM_NCDESTROY, sent },
+    { 0 }
+};
+/* Creation of a modal dialog that is resized inside WM_INITDIALOG (32) */
+static struct message WmCreateModalDialogResizeSeq[] = { /* FIXME: add */
+    /* (inside dialog proc, handling WM_INITDIALOG) */
+    { WM_WINDOWPOSCHANGING, sent },
+    { WM_NCCALCSIZE, sent },
+    { WM_NCACTIVATE, sent|parent|wparam, 0 },
+    { WM_GETTEXT, sent|defwinproc },
+    { WM_ACTIVATE, sent|parent|wparam, 0 },
+    { WM_WINDOWPOSCHANGING, sent },
+    { WM_WINDOWPOSCHANGING, sent|parent },
+    { WM_NCACTIVATE, sent|wparam, 1 },
+    { WM_ACTIVATE, sent|wparam, 1 },
+    { WM_WINDOWPOSCHANGED, sent },
+    { WM_SIZE, sent|defwinproc },
+    /* (setting focus) */
+    { WM_SHOWWINDOW, sent|wparam, 1 },
+    { WM_WINDOWPOSCHANGING, sent },
+    { WM_NCPAINT, sent },
+    { WM_GETTEXT, sent|defwinproc },
+    { WM_ERASEBKGND, sent },
+    { WM_CTLCOLORDLG, sent|defwinproc },
+    { WM_WINDOWPOSCHANGED, sent },
+    { WM_PAINT, sent },
+    /* (bunch of WM_CTLCOLOR* for each control) */
+    { WM_PAINT, sent|parent },
+    { WM_ENTERIDLE, sent|parent|wparam, 0 },
+    { WM_SETCURSOR, sent|parent },
+    { 0 }
+};
+
+static int sequence_cnt, sequence_size;
+static struct message* sequence;
+
+static void add_message(struct message msg)
+{
+    if (!sequence) 
+	sequence = malloc ( (sequence_size = 10) * sizeof (struct message) );
+    if (sequence_cnt == sequence_size) 
+	sequence = realloc ( sequence, (sequence_size *= 2) * sizeof (struct message) );
+    assert(sequence);
+    sequence[sequence_cnt++] = msg;
+}
+
+static void flush_sequence()
+{
+    free(sequence);
+    sequence = 0;
+    sequence_cnt = sequence_size = 0;
+}
+
+static void ok_sequence(struct message *expected, const char *context)
+{
+    static struct message end_of_sequence = { 0, 0, 0, 0 };
+    struct message *actual = sequence;
+    
+    add_message(end_of_sequence);
+
+    /* naive sequence comparison. Would be nice to use a regexp engine here */
+    while (expected->message || actual->message)
+    {
+	if (expected->message == actual->message)
+	{
+	    if (expected->flags & wparam)
+		 ok (expected->wParam == actual->wParam,
+		     "%s: in msg 0x%04x expecting wParam 0x%x got 0x%x\n",
+		     context, expected->message, expected->wParam, actual->wParam);
+	    if (expected->flags & lparam)
+		 ok (expected->lParam == actual->lParam,
+		     "%s: in msg 0x%04x expecting lParam 0x%lx got 0x%lx\n",
+		     context, expected->message, expected->lParam, actual->lParam);
+	    /* FIXME: should we check defwinproc? */
+	    ok ((expected->flags & (sent|posted)) == (actual->flags & (sent|posted)),
+		"%s: the msg 0x%04x should have been %s\n",
+		context, expected->message, (expected->flags & posted) ? "posted" : "sent");
+	    ok ((expected->flags & parent) == (actual->flags & parent),
+		"%s: the msg 0x%04x was expected in %s\n",
+		context, expected->message, (expected->flags & parent) ? "parent" : "child");
+	    expected++;
+	    actual++;
+	}
+	else if (expected->message && ((expected + 1)->message == actual->message) )
+	{
+	  todo_wine {
+	    ok (FALSE, "%s: the msg 0x%04x was not received\n", context, expected->message);
+	    expected++;
+	  }
+	}
+	else if (actual->message && (expected->message == (actual + 1)->message) )
+	{
+	  todo_wine {
+	    ok (FALSE, "%s: the msg 0x%04x was not expected\n", context, actual->message);
+	    actual++;
+	  }
+	}
+	else
+	{
+	  todo_wine {
+	    ok (FALSE, "%s: the msg 0x%04x was expected, but got msg 0x%04x instead\n",
+		context, expected->message, actual->message);
+	    expected++;
+	    actual++;
+	  }
+	}
+    }
+
+    flush_sequence();
+}
+
+/* test if we receive the right sequence of messages */
+static void test_messages(void)
+{
+    HWND hwnd, hparent, hchild;
+    HWND hchild2, hbutton;
+
+    hwnd = CreateWindowExA(0, "TestWindowClass", "Test overlapped", WS_OVERLAPPEDWINDOW,
+                           100, 100, 200, 200, 0, 0, 0, NULL);
+    ok (hwnd != 0, "Failed to create overlapped window\n");
+    ok_sequence(WmCreateOverlappedSeq, "CreateWindow:overlapped");
+    
+    ShowWindow(hwnd, TRUE);
+    ok_sequence(WmShowOverlappedSeq, "ShowWindow:overlapped");
+
+    DestroyWindow(hwnd);
+    ok_sequence(WmDestroyOverlappedSeq, "DestroyWindow:overlapped");
+
+    hparent = CreateWindowExA(0, "TestParentClass", "Test parent", WS_OVERLAPPEDWINDOW,
+                              100, 100, 200, 200, 0, 0, 0, NULL);
+    ok (hparent != 0, "Failed to create parent window\n");
+    flush_sequence();
+
+    hchild = CreateWindowExA(0, "TestWindowClass", "Test child", WS_CHILDWINDOW,
+                             0, 0, 10, 10, hparent, 0, 0, NULL);
+    ok (hchild != 0, "Failed to create child window\n");
+    ok_sequence(WmCreateChildSeq, "CreateWindow:child");
+    
+    hchild2 = CreateWindowExA(0, "SimpleWindowClass", "Test child2", WS_CHILDWINDOW,
+                               100, 100, 50, 50, hparent, 0, 0, NULL);
+    ok (hchild2 != 0, "Failed to create child2 window\n");
+    flush_sequence();
+
+    hbutton = CreateWindowExA(0, "TestWindowClass", "Test button", WS_CHILDWINDOW,
+                              0, 100, 50, 50, hchild, 0, 0, NULL);
+    ok (hbutton != 0, "Failed to create button window\n");
+    flush_sequence();
+
+    ShowWindow(hchild, TRUE);
+    ok_sequence(WmShowChildSeq, "ShowWindow:child");
+
+    MoveWindow(hchild, 10, 10, 20, 20, TRUE);
+    ok_sequence(WmResizingChildWithMoveWindowSeq, "MoveWindow:child");
+
+    DestroyWindow(hchild);
+    ok_sequence(WmDestroyChildSeq, "DestroyWindow:child");
+}
+
+static LRESULT WINAPI MsgCheckProcA(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+    struct message msg = { message, sent|wparam|lparam, wParam, lParam };
+
+    add_message(msg);
+    return DefWindowProcA(hwnd, message, wParam, lParam);
+}
+
+static BOOL RegisterWindowClasses(void)
+{
+    WNDCLASSA cls;
+
+    cls.style = 0;
+    cls.lpfnWndProc = MsgCheckProcA;
+    cls.cbClsExtra = 0;
+    cls.cbWndExtra = 0;
+    cls.hInstance = GetModuleHandleA(0);
+    cls.hIcon = 0;
+    cls.hCursor = LoadCursorA(0, (LPSTR)IDC_ARROW);
+    cls.hbrBackground = GetStockObject(WHITE_BRUSH);
+    cls.lpszMenuName = NULL;
+    cls.lpszClassName = "TestWindowClass";
+
+    if(!RegisterClassA(&cls)) return FALSE;
+
+    cls.style = 0;
+    cls.lpfnWndProc = DefWindowProcA;
+    cls.cbClsExtra = 0;
+    cls.cbWndExtra = 0;
+    cls.hInstance = GetModuleHandleA(0);
+    cls.hIcon = 0;
+    cls.hCursor = LoadCursorA(0, (LPSTR)IDC_ARROW);
+    cls.hbrBackground = GetStockObject(WHITE_BRUSH);
+    cls.lpszMenuName = NULL;
+    cls.lpszClassName = "TestParentClass";
+
+    if(!RegisterClassA(&cls)) return FALSE;
+
+    cls.style = 0;
+    cls.lpfnWndProc = DefWindowProcA;
+    cls.cbClsExtra = 0;
+    cls.cbWndExtra = 0;
+    cls.hInstance = GetModuleHandleA(0);
+    cls.hIcon = 0;
+    cls.hCursor = LoadCursorA(0, (LPSTR)IDC_ARROW);
+    cls.hbrBackground = GetStockObject(WHITE_BRUSH);
+    cls.lpszMenuName = NULL;
+    cls.lpszClassName = "SimpleWindowClass";
+
+    if(!RegisterClassA(&cls)) return FALSE;
+
+    return TRUE;
+}
+
+START_TEST(msg)
+{
+    if (!RegisterWindowClasses()) assert(0);
+
+    test_messages();
+}


-- 
Dimi.



[Index of Archives]     [Gimp for Windows]     [Red Hat]     [Samba]     [Yosemite Camping]     [Graphics Cards]     [Wine Home]

  Powered by Linux