summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDave Davenport <qball@gmpclient.org>2016-12-09 19:49:49 +0100
committerDave Davenport <qball@gmpclient.org>2016-12-09 19:49:49 +0100
commitd18f037d1c82f5081711753caec7d819000e0054 (patch)
treee74c04b1b38ff7f36b709d5f6cb15c1fe171050c
parentea28bcdc74dc4085b1c337dcb469aa641b3a8a6c (diff)
Add lex/bison parser for theme.
-rw-r--r--Makefile.am6
-rw-r--r--configure.ac4
-rw-r--r--doc/themer.md67
-rw-r--r--include/theme.h46
-rw-r--r--lexer/theme-lexer.l33
-rw-r--r--lexer/theme-parser.y163
-rw-r--r--source/rofi.c8
-rw-r--r--source/theme.c128
8 files changed, 455 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am
index 16d4493e..fbdb155c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3,6 +3,8 @@ AUTOMAKE_OPTIONS = 1.11.3
ACLOCAL_AMFLAGS = -I libgwater ${ACLOCAL_FLAGS}
+AM_YFLAGS = -d
+
noinst_LIBRARIES =
include $(top_srcdir)/libgwater-xcb-nolibtool.mk
@@ -27,6 +29,7 @@ rofi_SOURCES=\
source/helper.c\
source/timings.c\
source/history.c\
+ source/theme.c\
source/widgets/box.c\
source/widgets/widget.c\
source/widgets/textbox.c\
@@ -43,6 +46,8 @@ rofi_SOURCES=\
source/dialogs/window.c\
source/dialogs/script.c\
source/dialogs/help-keys.c\
+ lexer/theme-parser.y\
+ lexer/theme-lexer.l\
include/xcb.h\
include/xcb-internal.h\
include/rofi.h\
@@ -55,6 +60,7 @@ rofi_SOURCES=\
include/helper.h\
include/timings.h\
include/history.h\
+ include/theme.h\
include/widgets/box.h\
include/widgets/widget.h\
include/widgets/widget-internal.h\
diff --git a/configure.ac b/configure.ac
index c5df0ea6..ca32299e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3,6 +3,10 @@ AC_INIT([rofi], [1.2.0], [https://github.com/DaveDavenport/rofi/],[],[https://fo
AC_CONFIG_SRCDIR([source/rofi.c])
AC_CONFIG_HEADER([config.h])
+
+AC_PROG_LEX
+AC_PROG_YACC
+
dnl ---------------------------------------------------------------------
dnl Setup automake to be silent and in foreign mode.
dnl We want xz distribution
diff --git a/doc/themer.md b/doc/themer.md
new file mode 100644
index 00000000..46e12927
--- /dev/null
+++ b/doc/themer.md
@@ -0,0 +1,67 @@
+
+Each widget has:
+
+* Class: Type of widget.
+
+Example: textbox, scrollbar, separator
+
+Class are prefixed with a `@`
+
+* Name: Internal name of the widget.
+
+Sub-widgets are {Parent}.{Child}.
+
+Example: Listview, Listview.Even, Listview.Uneven, Listview.Scrollbar
+
+Names are prefixed with a `#`
+
+* State: State of widget
+
+Optional flag(s) indicating state.
+Multiple flags can be set.
+
+Example: Highlight Active Urgent
+
+States are prefixed with a `!`
+
+So to set color of Even entry in listview that is highlighted and urgent:
+
+`@textbox #Listview.Even !Urgent !Highlight`
+
+Or to indicate all textboxes
+
+`@textbox !Highlight`
+
+Class is manditory, name is optional. Name is split on .s.
+
+
+# Internally:
+
+The theme is represented like a tree:
+
+class --> name --> name --> state -> state
+
+The states are sorted alphabetically
+
+So `@textbox #Listview.Even !Urgent !Highlight` becomes:
+
+textbox->listview->even -> highlight -> urgent.
+
+When searching for entries the tree is traversed until deepest node is found.
+Missing states are skipped.
+Then from there properties are searched going up again.
+
+Properties are in the form of:
+
+`name: value`
+Each property ends with `;`
+Each property has a type. (Boolean, Integer, String, Color)
+
+A block is enclosed by `{}`
+
+```
+@textbox #Listview.Even !Urgent !Highlight {
+ padding: 3;
+ foreground: #aarrggbb;
+}
+```
diff --git a/include/theme.h b/include/theme.h
new file mode 100644
index 00000000..dae85d9c
--- /dev/null
+++ b/include/theme.h
@@ -0,0 +1,46 @@
+#ifndef THEME_H
+#define THEME_H
+#include <glib.h>
+typedef enum {
+ P_INTEGER,
+ P_FLOAT,
+ P_STRING,
+ P_BOOLEAN,
+ P_COLOR
+} PropertyType;
+
+typedef struct {
+ char *name;
+ PropertyType type;
+ union {
+ int i;
+ double f;
+ char *s;
+ int b;
+ unsigned int color;
+ } value;
+} Property;
+
+typedef struct _Widget {
+ char *name;
+
+ unsigned int num_widgets;
+ struct _Widget **widgets;
+
+ GHashTable *properties;
+
+ struct _Widget *parent;
+} Widget;
+
+extern Widget *rofi_theme;
+
+Widget *rofi_theme_find_or_create_class ( Widget *base, const char *class );
+
+
+void rofi_theme_print ( Widget *widget );
+
+Property *rofi_theme_property_create ( PropertyType type );
+void rofi_theme_property_free ( Property *p );
+void rofi_theme_free ( Widget * );
+void rofi_theme_parse_file ( const char *file );
+#endif
diff --git a/lexer/theme-lexer.l b/lexer/theme-lexer.l
new file mode 100644
index 00000000..4094a256
--- /dev/null
+++ b/lexer/theme-lexer.l
@@ -0,0 +1,33 @@
+%option noyywrap
+
+%{
+#include <stdio.h>
+
+
+#include "lexer/theme-parser.tab.h"
+int yylex(void);
+#define YY_DECL int yylex()
+
+%}
+
+%%
+
+"@" { return CLASS;}
+"\{" { return BOPEN;}
+"\}" { return BCLOSE;}
+":" { return PSEP; }
+";" { return PCLOSE;}
+"." { return NSEP; }
+[ \t] ; // ignore all whitespace
+[0-9]+\.[0-9]+ { yylval.fval = g_ascii_strtod(yytext, NULL); return T_FLOAT;}
+[0-9]+ { yylval.ival = (int)g_ascii_strtoll(yytext, NULL, 10); return T_INT;}
+(true|false) { yylval.bval= g_strcmp0(yytext, "true") == 0; return T_BOOLEAN;}
+[a-zA-Z0-9]+ { yylval.sval = g_strdup(yytext); return N_STRING;}
+\"[a-zA-Z0-9]+\" { yylval.sval = g_strdup(yytext); return T_STRING;}
+#[0-9A-Fa-f]+ { yylval.colorval = (unsigned int)strtoull ( &yytext[1], NULL, 16); return T_COLOR;}
+[\r\n]+ ;
+
+<*><<EOF>> {
+ yyterminate();
+}
+%%
diff --git a/lexer/theme-parser.y b/lexer/theme-parser.y
new file mode 100644
index 00000000..b30803cc
--- /dev/null
+++ b/lexer/theme-parser.y
@@ -0,0 +1,163 @@
+%locations
+%debug
+%error-verbose
+
+%code requires {
+#include "theme.h"
+}
+%{
+#include <stdio.h>
+#include <stdlib.h>
+
+void yyerror(const char* s);
+int yylex (void );
+
+#include "theme.h"
+Widget *rofi_theme = NULL;
+%}
+
+%union {
+ int ival;
+ double fval;
+ char *sval;
+ int bval;
+ unsigned int colorval;
+ Widget *theme;
+ GList *name_path;
+ Property *property;
+ GHashTable *property_list;
+}
+
+%token <ival> T_INT
+%token <fval> T_FLOAT
+%token <sval> T_STRING
+%token <sval> N_STRING
+%token <bval> T_BOOLEAN
+%token <colorval> T_COLOR
+
+%token CLASS "class";
+%token BOPEN "bracket open";
+%token BCLOSE "bracket close";
+%token PSEP "property separator";
+%token PCLOSE "property close";
+%token NSEP "Name separator";
+
+%type <sval> class
+%type <sval> entry
+%type <sval> pvalue
+%type <theme> entries
+%type <theme> start
+%type <name_path> name_path
+%type <property> property
+%type <property_list> property_list
+%type <property_list> properties
+%type <property_list> optional_properties
+%start start
+
+%%
+
+start:
+ optional_properties
+ entries {
+ $$ = $2;
+ if ( $1 != NULL ) {
+ $$->properties = $1;
+ }
+ }
+;
+entries:
+ %empty {
+ // There is always a base widget.
+ $$ = rofi_theme = (Widget*)g_malloc0 (sizeof(Widget));
+ rofi_theme->name = g_strdup ( "Window" );
+ }
+| entries entry { $$ = $1; }
+;
+
+entry:
+ class
+ name_path
+ properties
+{
+ Widget *widget = rofi_theme_find_or_create_class ( rofi_theme , $1 );
+ g_free($1);
+ for ( GList *iter = g_list_first ( $2 ); iter ; iter = g_list_next ( iter ) ) {
+ widget = rofi_theme_find_or_create_class ( widget, iter->data );
+ }
+ g_list_foreach ( $2, (GFunc)g_free , NULL );
+ g_list_free ( $2 );
+ if ( widget->properties != NULL ) {
+ fprintf(stderr, "Properties already set on this widget.\n");
+ exit ( EXIT_FAILURE );
+ }
+ widget->properties = $3;
+
+};
+
+/**
+ * properties
+ */
+optional_properties
+ : %empty { $$ = NULL; }
+ | property_list { $$ = $1; }
+;
+properties: BOPEN property_list BCLOSE { $$ = $2;}
+ | BOPEN BCLOSE { $$ = NULL; }
+ | %empty { $$ = NULL; }
+ ;
+
+property_list:
+ property {
+ $$ = g_hash_table_new_full ( g_str_hash, g_str_equal, NULL, (GDestroyNotify)rofi_theme_property_free );
+ g_hash_table_replace ( $$, $1->name, $1 );
+ }
+| property_list property {
+ // Old will be free'ed, and key/value will be replaced.
+ g_hash_table_replace ( $$, $2->name, $2 );
+ }
+;
+
+property
+: pvalue PSEP T_INT PCLOSE {
+ $$ = rofi_theme_property_create ( P_INTEGER );
+ $$->name = $1;
+ $$->value.i = $3;
+ }
+| pvalue PSEP T_FLOAT PCLOSE {
+ $$ = rofi_theme_property_create ( P_FLOAT );
+ $$->name = $1;
+ $$->value.f = $3;
+ }
+| pvalue PSEP T_COLOR PCLOSE {
+ $$ = rofi_theme_property_create ( P_COLOR );
+ $$->name = $1;
+ $$->value.color = $3;
+ }
+| pvalue PSEP T_STRING PCLOSE {
+ $$ = rofi_theme_property_create ( P_STRING );
+ $$->name = $1;
+ $$->value.s = $3;
+ }
+| pvalue PSEP T_BOOLEAN PCLOSE {
+ $$ = rofi_theme_property_create ( P_BOOLEAN );
+ $$->name = $1;
+ $$->value.b = $3;
+ }
+;
+
+pvalue: N_STRING { $$ = $1; }
+
+class:
+ CLASS N_STRING { $$ = $2; }
+;
+
+
+name_path:
+ %empty { $$ = NULL; }
+| N_STRING { $$ = g_list_append ( NULL, $1 );}
+| name_path NSEP N_STRING { $$ = g_list_append ( $1, $3);}
+;
+
+
+%%
+
diff --git a/source/rofi.c b/source/rofi.c
index f89c64d4..7fb2d28d 100644
--- a/source/rofi.c
+++ b/source/rofi.c
@@ -64,6 +64,8 @@
#include "gitconfig.h"
+#include "theme.h"
+
// Pidfile.
char *pidfile = NULL;
const char *cache_dir = NULL;
@@ -896,6 +898,12 @@ int main ( int argc, char *argv[] )
// Parse command line for settings, independent of other -no-config.
config_parse_cmd_options_dynamic ( );
+ char *theme = NULL;
+ if ( find_arg_str ( "-theme", &theme ) > 0 ){
+ rofi_theme_parse_file ( theme );
+ rofi_theme_print ( rofi_theme );
+ }
+
// Dump.
// catch help request
if ( find_arg ( "-h" ) >= 0 || find_arg ( "-help" ) >= 0 || find_arg ( "--help" ) >= 0 ) {
diff --git a/source/theme.c b/source/theme.c
new file mode 100644
index 00000000..7fb05816
--- /dev/null
+++ b/source/theme.c
@@ -0,0 +1,128 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include "theme.h"
+
+void yyerror ( const char *);
+Widget *rofi_theme_find_or_create_class ( Widget *base, const char *class )
+{
+ for ( unsigned int i = 0; i < base->num_widgets;i++){
+ if ( g_strcmp0(base->widgets[i]->name, class) == 0 ){
+ return base->widgets[i];
+ }
+ }
+
+ base->widgets = g_realloc ( base->widgets, sizeof(Widget*)*(base->num_widgets+1));
+ base->widgets[base->num_widgets] = g_malloc0(sizeof(Widget));
+ Widget *retv = base->widgets[base->num_widgets];
+ retv->parent = base;
+ retv->name = g_strdup(class);
+ base->num_widgets++;
+ return retv;
+}
+/**
+ * Properties
+ */
+Property *rofi_theme_property_create ( PropertyType type )
+{
+ Property *retv = g_malloc0 ( sizeof(Property) );
+ retv->type = type;
+ return retv;
+}
+void rofi_theme_property_free ( Property *p )
+{
+ if ( p == NULL ) {
+ return;
+ }
+ g_free ( p->name );
+ if ( p->type == P_STRING ) {
+ g_free ( p->value.s );
+ }
+ g_free(p);
+}
+
+void rofi_theme_free ( Widget *widget )
+{
+ if ( widget == NULL ){
+ return;
+ }
+ if ( widget->properties ) {
+ g_hash_table_destroy ( widget->properties );
+ }
+ for ( unsigned int i = 0; i < widget->num_widgets; i++ ){
+ rofi_theme_free ( widget->widgets[i] );
+ }
+ g_free ( widget->widgets );
+ g_free ( widget->name );
+ g_free ( widget );
+}
+
+/**
+ * print
+ */
+static void rofi_theme_print_property_index ( int depth, Property *p )
+{
+ printf("%*s %s: ", depth, "", p->name );
+ switch ( p->type )
+ {
+ case P_STRING:
+ printf("\"%s\"", p->value.s);
+ break;
+ case P_INTEGER:
+ printf("%d", p->value.i);
+ break;
+ case P_FLOAT:
+ printf("%.2f", p->value.f);
+ break;
+ case P_BOOLEAN:
+ printf("%s", p->value.b?"true":"false");
+ break;
+ case P_COLOR:
+ printf("#%08X", p->value.color);
+ break;
+ }
+ putchar ( '\n' );
+}
+
+static void rofi_theme_print_index ( int depth, Widget *widget )
+{
+ printf ( "%*sName: %s \n", depth, "", widget->name );
+
+ GHashTableIter iter;
+ gpointer key, value;
+ if ( widget->properties ){
+ g_hash_table_iter_init (&iter, widget->properties);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ Property *p = (Property*)value;
+ rofi_theme_print_property_index ( depth, p );
+ }
+ }
+ for ( unsigned int i = 0; i < widget->num_widgets;i++){
+ rofi_theme_print_index ( depth+2, widget->widgets[i] );
+ }
+}
+void rofi_theme_print ( Widget *widget )
+{
+ rofi_theme_print_index ( 0, widget);
+}
+
+extern int yyparse();
+extern FILE* yyin;
+extern Widget *rofi_theme;
+
+void rofi_theme_parse_file ( const char *file )
+{
+ yyin = fopen ( file, "rb");
+ if ( yyin == NULL ){
+ fprintf(stderr, "Failed to open file: '%s'\n", strerror ( errno ) );
+ return;
+ }
+ while ( yyparse() );
+}
+
+void yyerror(const char* s) {
+ fprintf(stderr, "Parse error: %s\n", s);
+ exit(EXIT_FAILURE);
+}