[ALL] New Feature: Add XPM Support (#1)

This commit is contained in:
Martin Duquesnoy 2008-10-12 20:35:17 +02:00
parent 9c197af29c
commit 1d6ba3ff69
11 changed files with 264 additions and 178 deletions

View File

@ -31,7 +31,8 @@ set(wmfs_src
util.c
layout.c
tag.c
bar.c)
bar.c
draw.c)
# Set the executable from the wmfs_src
add_executable(wmfs ${wmfs_src})

2
TODO
View File

@ -1,7 +1,7 @@
· Add Doxygen comment
· Mouse bindings in the config file
· Can change client position in the tile grid
· Add images support
· Add image support <- *.xpm: OK for now.
· Multi-Head support
· Fix all the bug \o/
· XCB ?

126
bar.c
View File

@ -33,7 +33,7 @@
#include "wmfs.h"
BarWindow*
bar_create(int x, int y, uint w, uint h, int bord, uint color)
bar_create(int x, int y, uint w, uint h, int bord, uint color, Bool entermask)
{
XSetWindowAttributes at;
BarWindow *bw;
@ -42,7 +42,10 @@ bar_create(int x, int y, uint w, uint h, int bord, uint color)
at.override_redirect = 1;
at.background_pixmap = ParentRelative;
at.event_mask = ButtonPressMask | ExposureMask | EnterWindowMask;
if(entermask)
at.event_mask = ButtonPressMask | ExposureMask | EnterWindowMask;
else
at.event_mask = ButtonPressMask | ExposureMask;
bw->dr = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
bw->win = XCreateWindow(dpy, root, x, y, w, h, bord, DefaultDepth(dpy, screen),
@ -62,6 +65,7 @@ bar_delete(BarWindow *bw)
{
XDestroyWindow(dpy, bw->win);
XFreePixmap(dpy, bw->dr);
free(bw);
return;
}
@ -90,8 +94,7 @@ bar_moveresize(BarWindow *bw, int x, int y, uint w, uint h)
void
bar_refresh_color(BarWindow *bw)
{
XSetForeground(dpy, gc, bw->color);
XFillRectangle(dpy, bw->dr, gc, 0, 0, bw->w, bw->h);
draw_rectangle(bw->dr, 0, 0, bw->w, bw->h, bw->color);
return;
}
@ -104,61 +107,6 @@ bar_refresh(BarWindow *bw)
return;
}
void
draw_taglist(Drawable dr)
{
int i;
char buf[conf.ntag][256];
char p[4];
taglen[0] = PAD/2;
for(i = 0; i < conf.ntag; ++i)
{
/* Make the tags string */
ITOA(p, clientpertag(i+1));
sprintf(buf[i], "%s<%s>", tags[i+1].name, (clientpertag(i+1)) ? p : "");
/* Draw the string */
xprint(dr, taglen[i], fonth,
((i+1 == seltag) ? conf.colors.tagselfg : conf.colors.text),
((i+1 == seltag) ? conf.colors.tagselbg : conf.colors.bar), PAD, buf[i]);
/* Draw the tag border */
XSetForeground(dpy, gc, conf.colors.tagbord);
XFillRectangle(dpy, dr, gc,
taglen[i] + textw(buf[i]) + PAD/2,
0, conf.tagbordwidth, barheight);
/* Edit taglen[i+1] for the next time */
taglen[i+1] = taglen[i] + textw(buf[i]) + PAD + conf.tagbordwidth;
}
return;
}
void
draw_layoutsym(int x, int y)
{
if(!layoutsym)
{
layoutsym = bar_create(x, y,
textw(tags[seltag].layout.symbol) + BPAD,
barheight, 0, conf.colors.layout_bg);
XMapRaised(dpy, layoutsym->win);
}
bar_refresh_color(layoutsym);
bar_moveresize(layoutsym, x, y, textw(tags[seltag].layout.symbol) + BPAD, barheight);
xprint(layoutsym->dr, BPAD/2, fonth, conf.colors.layout_fg,
conf.colors.layout_bg, BPAD, tags[seltag].layout.symbol);
bar_refresh(layoutsym);
return;
}
/* Top/Bottom Bar Manage Function */
void
updatebar(void)
@ -170,18 +118,18 @@ updatebar(void)
draw_taglist(bar->dr);
/* Draw layout symbol */
draw_layoutsym(taglen[conf.ntag], bary);
draw_layout(taglen[conf.ntag], bary + (!conf.bartop));
/* Draw status text */
xprint(bar->dr, mw - textw(bartext), fonth, conf.colors.text, conf.colors.bar, 0, bartext);
draw_text(bar->dr, mw - textw(bartext), fonth, conf.colors.text, conf.colors.bar, 0, bartext);
/* Bar border */
if(conf.tagbordwidth)
{
XSetForeground(dpy, gc, conf.colors.tagbord);
XFillRectangle(dpy, bar->dr, gc, 0,
((conf.bartop) ? barheight-1: 0), mw, 1);
XFillRectangle(dpy, bar->dr, gc, mw - textw(bartext) - 5, 0, 1, barheight);
draw_rectangle(bar->dr, 0, ((conf.bartop) ? barheight-1: 0),
mw, 1, conf.colors.tagbord);
draw_rectangle(bar->dr, mw - textw(bartext) - 5,
0, conf.tagbordwidth, barheight, conf.colors.tagbord);
}
/* Refresh the bar */
@ -202,7 +150,7 @@ updatebutton(Bool c)
int i, j, x, pm = 0;
int y = 0, hi = 0;
j = taglen[conf.ntag] + textw(tags[seltag].layout.symbol) + PAD;
j = taglen[conf.ntag] + get_image_attribute(tags[seltag].layout.image)->width + PAD / 2;
if(!conf.bartop)
y = bary + 1;
@ -223,7 +171,7 @@ updatebutton(Bool c)
{
conf.barbutton[i].bw = bar_create(x, y, textw(conf.barbutton[i].text) + BPAD,
barheight + hi, 0,
conf.barbutton[i].bg_color);
conf.barbutton[i].bg_color, False);
XMapRaised(dpy, conf.barbutton[i].bw->win);
}
@ -232,7 +180,7 @@ updatebutton(Bool c)
bar_refresh_color(conf.barbutton[i].bw);
bar_moveresize(conf.barbutton[i].bw, x, y, textw(conf.barbutton[i].text) + BPAD, barheight + hi);
xprint(conf.barbutton[i].bw->dr, BPAD/2, fonth, conf.barbutton[i].fg_color,
draw_text(conf.barbutton[i].bw->dr, BPAD/2, fonth, conf.barbutton[i].fg_color,
conf.barbutton[i].bg_color, BPAD, conf.barbutton[i].text);
bar_refresh(conf.barbutton[i].bw);
}
@ -240,3 +188,45 @@ updatebutton(Bool c)
return;
}
void
uicb_togglebarpos(uicb_t cmd)
{
int i;
conf.bartop = !conf.bartop;
if(conf.bartop)
bary = 0;
else
bary = mh - barheight;
bar_moveresize(bar, 0, bary, mw, barheight);
updatebar();
for(i = 0; i < conf.nbutton; ++i)
XUnmapWindow(dpy, conf.barbutton[i].bw->win);
updatebutton(False);
for(i = 0; i < conf.nbutton; ++i)
XMapWindow(dpy, conf.barbutton[i].bw->win);
arrange();
return;
}
void
updatetitlebar(Client *c)
{
XFetchName(dpy, c->win, &(c->title));
if(!c->title)
c->title = strdup("WMFS");
if(conf.ttbarheight > 10)
{
bar_refresh_color(c->tbar);
draw_text(c->tbar->dr, 3, ((fonth - xftfont->descent) + ((conf.ttbarheight - fonth) / 2)),
((c == sel) ? conf.colors.ttbar_text_focus : conf.colors.ttbar_text_normal),
conf.colors.bar, 0, c->title);
bar_refresh(c->tbar);
}
return;
}

View File

@ -92,6 +92,7 @@ name_to_func(char *name, func_name_list_t l[])
for(i = 0; l[i].name ; ++i)
if(!strcmp(name, l[i].name))
return l[i].func;
return NULL;
}
@ -104,6 +105,7 @@ char_to_modkey(char *name)
for(i = 0; key_list[i].name; ++i)
if(!strcmp(name, key_list[i].name))
return key_list[i].keysym;
return NoSymbol;
}
@ -116,6 +118,7 @@ char_to_button(char *name)
for(i = 0; mouse_button_list[i].name; ++i)
if(!strcmp(name, mouse_button_list[i].name))
return mouse_button_list[i].button;
return 0;
}
@ -127,6 +130,7 @@ layout_name_to_struct(Layout lt[], char *name)
for(i = 0; i < MAXLAYOUT; ++i)
if(lt[i].func == name_to_func(name, layout_list))
return lt[i];
return lt[0];
}
@ -146,6 +150,8 @@ var_to_str(char *conf_choice)
return strdup(tmpchar);
else
return strdup(conf_choice);
return NULL;
}
@ -186,7 +192,7 @@ init_conf(void)
static cfg_opt_t layout_opts[] =
{
CFG_STR("type", "", CFGF_NONE),
CFG_STR("symbol", "", CFGF_NONE),
CFG_STR("image", "", CFGF_NONE),
CFG_END()
};
@ -356,11 +362,11 @@ init_conf(void)
{
fprintf(stderr, "WMFS Configuration: Too much or no layouts\n");
conf.nlayout = 1;
conf.layout[0].symbol = strdup("TILE");
conf.layout[0].image = strdup("TILE");
conf.layout[0].func = tile;
}
if(!conf.layout[0].symbol
if(!conf.layout[0].image
&& !conf.layout[0].func)
{
for(i = 0; i < conf.nlayout; ++i)
@ -374,7 +380,7 @@ init_conf(void)
}
else
{
conf.layout[i].symbol = strdup(var_to_str(cfg_getstr(cfgtmp, "symbol")));
conf.layout[i].image = strdup(var_to_str(cfg_getstr(cfgtmp, "image")));
conf.layout[i].func = name_to_func(strdup(cfg_getstr(cfgtmp, "type")), layout_list);
}
}

163
draw.c Normal file
View File

@ -0,0 +1,163 @@
/*
* draw.c
* Copyright © 2008 Martin Duquesnoy <xorg62@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of the nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "wmfs.h"
void
draw_text(Drawable d, int x, int y, char* fg, uint bg, int pad, char *str)
{
XftColor xftcolor;
XftDraw *xftd;
/* Transform X Drawable -> Xft Drawable */
xftd = XftDrawCreate(dpy, d, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen));
/* Color the text font */
draw_rectangle(d, x - pad/2, 0, textw(str) + pad, barheight, bg);
/* Alloc text color */
XftColorAllocName(dpy, DefaultVisual(dpy, screen),
DefaultColormap(dpy, screen), fg, &xftcolor);
/* Draw the text */
XftDrawStringUtf8(xftd, &xftcolor, xftfont, x, y, (FcChar8 *)str, strlen(str));
/* Free the text color */
XftColorFree(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen), &xftcolor);
return;
}
/* Drawing image function :
* Only *.xpm file for now. */
void
draw_image(Drawable dr, int x, int y, char *file)
{
XImage *img;
if (XpmReadFileToImage(dpy, file, &img, NULL, NULL)) {
fprintf(stderr, "WMFS Error: reading xpm file %s\n", file);
exit(EXIT_FAILURE);
}
XPutImage(dpy, dr, gc, img, 0, 0, x, y, img->width, img->height);
XFree(img);
return;
}
void
draw_taglist(Drawable dr)
{
int i;
char buf[conf.ntag][256];
char p[4];
taglen[0] = PAD/2;
for(i = 0; i < conf.ntag; ++i)
{
/* Make the tags string */
ITOA(p, clientpertag(i+1));
sprintf(buf[i], "%s<%s>", tags[i+1].name, (clientpertag(i+1)) ? p : "");
/* Draw the string */
draw_text(dr, taglen[i], fonth,
((i+1 == seltag) ? conf.colors.tagselfg : conf.colors.text),
((i+1 == seltag) ? conf.colors.tagselbg : conf.colors.bar), PAD, buf[i]);
/* Draw the tag border */
draw_rectangle(dr, taglen[i] + textw(buf[i]) + PAD/2,
0, conf.tagbordwidth, barheight, conf.colors.tagbord);
/* Edit taglen[i+1] for the next time */
taglen[i+1] = taglen[i] + textw(buf[i]) + PAD + conf.tagbordwidth;
}
return;
}
void
draw_layout(int x, int y)
{
if(!layoutsym)
{
layoutsym = bar_create(x, y,
get_image_attribute(tags[seltag].layout.image)->width,
barheight-1, 0, conf.colors.bar, False);
XMapRaised(dpy, layoutsym->win);
}
bar_refresh_color(layoutsym);
bar_moveresize(layoutsym, x, y, get_image_attribute(tags[seltag].layout.image)->width, barheight-1);
draw_image(layoutsym->dr, 0,
(barheight)/2 - get_image_attribute(tags[seltag].layout.image)->height/2,
tags[seltag].layout.image);
bar_refresh(layoutsym);
return;
}
void
draw_rectangle(Drawable dr, int x, int y, uint w, uint h, uint color)
{
XRectangle r = { x, y, w, h };
XSetForeground(dpy, gc, color);
XFillRectangles(dpy, dr, gc, &r, 1);
return;
}
XImage*
get_image_attribute(char *file)
{
XImage *ret;
if (XpmReadFileToImage(dpy, file, &ret, NULL, NULL)) {
fprintf(stderr, "WMFS Error: reading xpm file %s\n", file);
exit(EXIT_FAILURE);
}
return ret;
}
ushort
textw(const char *text)
{
XGlyphInfo gl;
XftTextExtentsUtf8(dpy, xftfont, (FcChar8 *)text, strlen(text), &gl);
return gl.width + xftfont->descent;
}

View File

@ -310,10 +310,11 @@ expose(XEvent ev)
if(ev.xexpose.count == 0
&& (ev.xexpose.window == bar->win))
updatebar();
if(conf.ttbarheight)
for(c = clients; c; c = c->next)
if(ev.xexpose.window == c->tbar->win)
updatetitle(c);
updatetitlebar(c);
return;
}
@ -401,7 +402,7 @@ propertynotify(XEvent ev)
}
if(ev.xproperty.atom == XA_WM_NAME
|| ev.xproperty.atom == net_atom[NetWMName])
updatetitle(c);
updatetitlebar(c);
}
return;

View File

@ -60,7 +60,7 @@ layoutswitch(Bool b)
for(i = 0; i < conf.nlayout; ++i)
{
if(tags[seltag].layout.symbol == conf.layout[i].symbol
if(tags[seltag].layout.image == conf.layout[i].image
&& tags[seltag].layout.func == conf.layout[i].func)
{
if(b)

36
util.c
View File

@ -77,39 +77,3 @@ uicb_spawn(uicb_t cmd)
return;
}
ushort
textw(const char *text)
{
XGlyphInfo gl;
XftTextExtentsUtf8(dpy, xftfont, (FcChar8 *)text, strlen(text), &gl);
return gl.width + xftfont->descent;
}
void
xprint(Drawable d, int x, int y, char* fg, uint bg, int pad, char *str)
{
XftColor xftcolor;
XftDraw *xftd;
/* Transform X Drawable -> Xft Drawable */
xftd = XftDrawCreate(dpy, d, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen));
/* Color the text font */
XSetForeground(dpy, gc, bg);
XFillRectangle(dpy, d, gc, x - pad/2, 0, textw(str) + pad, barheight);
/* Alloc text color */
XftColorAllocName(dpy, DefaultVisual(dpy, screen),
DefaultColormap(dpy, screen), fg, &xftcolor);
/* Draw the text */
XftDrawStringUtf8(xftd, &xftcolor, xftfont, x, y, (FcChar8 *)str, strlen(str));
/* Free the text color */
XftColorFree(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen), &xftcolor);
return;
}

60
wmfs.c
View File

@ -43,7 +43,8 @@ arrange(void)
else
hide(c);
tags[seltag].layout.func();
if(sel)
tags[seltag].layout.func();
focus(selbytag[seltag]);
updatebar();
@ -191,14 +192,14 @@ focus(Client *c)
if(conf.raisefocus)
raiseclient(c);
XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime);
updatetitle(c);
updatetitlebar(c);
}
else
XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
for(cc = clients; cc; cc = cc->next)
if(!ishide(cc))
updatetitle(cc);
updatetitlebar(cc);
return;
}
@ -399,7 +400,7 @@ init(void)
/* INIT BAR / BUTTON */
bary = (conf.bartop) ? 0 : mh - barheight;
bar = bar_create(0, bary, mw, barheight, 0, conf.colors.bar);
bar = bar_create(0, bary, mw, barheight, 0, conf.colors.bar, False);
XMapRaised(dpy, bar->win);
strcpy(bartext, "WMFS-" WMFS_VERSION);
updatebutton(False);
@ -538,7 +539,7 @@ manage(Window w, XWindowAttributes *wa)
c->tbar = bar_create(c->x, c->y - conf.ttbarheight,
c->w, c->h,conf.borderheight,
conf.colors.bar);
conf.colors.bar, True);
/* Basic window for close button... */
if(conf.ttbarheight > 5)
@ -554,7 +555,7 @@ manage(Window w, XWindowAttributes *wa)
XSelectInput(dpy, w, EnterWindowMask | FocusChangeMask
| PropertyChangeMask | StructureNotifyMask);
setsizehints(c);
updatetitle(c);
updatetitlebar(c);
if((rettrans = XGetTransientForHint(dpy, w, &trans) == Success))
for(t = clients; t && t->win != trans; t = t->next);
if(t)
@ -688,7 +689,7 @@ moveresize(Client *c, int x, int y, int w, int h, bool r)
if(conf.ttbarheight > 5)
XMoveWindow(dpy, c->button, BUTX(x, w), BUTY(y));
}
updatetitle(c);
updatetitlebar(c);
XSync(dpy, False);
}
@ -715,7 +716,7 @@ raiseclient(Client *c)
XRaiseWindow(dpy, c->tbar->win);
if(conf.ttbarheight > 5)
XRaiseWindow(dpy, c->button);
updatetitle(c);
updatetitlebar(c);
}
return;
@ -836,29 +837,6 @@ setsizehints(Client *c)
return;
}
void
uicb_togglebarpos(uicb_t cmd)
{
int i;
conf.bartop = !conf.bartop;
if(conf.bartop)
bary = 0;
else
bary = mh - barheight;
bar_moveresize(bar, 0, bary, mw, barheight);
updatebar();
for(i = 0; i < conf.nbutton; ++i)
XUnmapWindow(dpy, conf.barbutton[i].bw->win);
updatebutton(False);
for(i = 0; i < conf.nbutton; ++i)
XMapWindow(dpy, conf.barbutton[i].bw->win);
arrange();
return;
}
void
unhide(Client *c)
{
@ -925,25 +903,6 @@ unmapclient(Client *c)
return;
}
void
updatetitle(Client *c)
{
XFetchName(dpy, c->win, &(c->title));
if(!c->title)
c->title = strdup("WMFS");
if(conf.ttbarheight > 10)
{
bar_refresh_color(c->tbar);
xprint(c->tbar->dr, 3, ((fonth - xftfont->descent) + ((conf.ttbarheight - fonth) / 2)),
((c == sel) ? conf.colors.ttbar_text_focus : conf.colors.ttbar_text_normal),
conf.colors.bar, 0, c->title);
bar_refresh(c->tbar);
}
return;
}
int
main(int argc, char **argv)
{
@ -999,7 +958,6 @@ main(int argc, char **argv)
mainloop();
/* Exiting WMFS :'( */
XUngrabKey(dpy, AnyKey, AnyModifier, root);
XftFontClose(dpy, xftfont);
XFreeCursor(dpy, cursor[CurNormal]);
XFreeCursor(dpy, cursor[CurMove]);

25
wmfs.h
View File

@ -49,6 +49,7 @@
#include <X11/cursorfont.h>
#include <X11/Xutil.h>
#include <X11/Xft/Xft.h>
#include <X11/xpm.h>
#include <confuse.h>
#include "config.h"
@ -132,7 +133,7 @@ typedef struct
/* Layout Structure */
typedef struct
{
char *symbol;
char *image;
void (*func)(void);
} Layout;
@ -216,15 +217,24 @@ enum { NetSupported, NetWMName, NetLast };
/* Functions Prototypes */
/* bar.c */
BarWindow* bar_create(int x, int y, uint w, uint h, int bord, uint color);
BarWindow* bar_create(int x, int y, uint w, uint h, int bord, uint color, Bool entermask);
void bar_delete(BarWindow *bw);
void bar_moveresize(BarWindow *bw, int x, int y, uint w, uint h);
void bar_refresh_color(BarWindow *bw);
void bar_refresh(BarWindow *bw);
void draw_taglist(Drawable dr);
void draw_layoutsym(int x, int y);
void updatebar(void);
void updatebutton(Bool c);
void updatetitlebar(Client *c);
void uicb_togglebarpos(uicb_t);
/* draw.c */
void draw_text(Drawable d, int x, int y, char* fg, uint bg, int pad, char *str);
void draw_image(Drawable dr, int x, int y, char *file);
void draw_taglist(Drawable dr);
void draw_layout(int x, int y);
void draw_rectangle(Drawable dr, int x, int y, uint w, uint h, uint color);
XImage* get_image_attribute(char *file);
ushort textw(const char *text);
/* config.c */
void init_conf(void);
@ -246,10 +256,6 @@ void getevent(void);
/* util.c */
void *emalloc(uint elemet, uint size);
ulong getcolor(char *color);
ushort textw(const char *text);
void xprint(Drawable d, int x, int y,
char* fg, uint bg,
int pad, char* str);
void uicb_spawn(uicb_t);
/* tag.c */
@ -302,12 +308,10 @@ void setsizehints(Client *c);
void unhide(Client *c);
void unmanage(Client *c);
void unmapclient(Client *c);
void updatetitle(Client *c);
void uicb_client_prev(uicb_t);
void uicb_client_next(uicb_t);
void uicb_killclient(uicb_t);
void uicb_quit(uicb_t);
void uicb_togglebarpos(uicb_t);
/* Variables */
@ -331,7 +335,6 @@ Cursor cursor[CurLast];
int fonth;
XftFont *xftfont;
/* Bar / Tags */
BarWindow *bar;
BarWindow *layoutsym;

6
wmfsrc
View File

@ -46,9 +46,9 @@ colors
layouts
{
layout { type = "tile" symbol = "TILE" }
layout { type = "max" symbol = "MAX" }
layout { type = "free" symbol = "FREE" }
layout { type = "tile" image = "/" }
layout { type = "max" image = "/" }
layout { type = "free" image = "/" }
}
tags