doc/disorder-decode.8.html
doc/disorder-decode.8
doc/plumbing.png
+disobedience/manual.h
+disobedience/manual.html
disobedience_SOURCES=disobedience.h disobedience.c client.c queue.c \
choose.c misc.c style.h control.c properties.c menu.c \
- log.c progress.c login.c rtp.c \
+ log.c progress.c login.c rtp.c help.c \
../lib/memgc.c
disobedience_LDADD=../lib/libdisorder.a $(LIBPCRE) $(LIBGC) $(LIBGCRYPT)
disobedience_LDFLAGS=$(GTK_LIBS)
disobedience.o: style.h
style.h: ${srcdir}/disobedience.rc ${top_srcdir}/scripts/text2c
- ${top_srcdir}/scripts/text2c style ${srcdir}/disobedience.rc > style.h.tmp
- mv style.h.tmp style.h
+ ${top_srcdir}/scripts/text2c style ${srcdir}/disobedience.rc > $@.tmp
+ mv $@.tmp $@
+
+manual.html: ../doc/disobedience.1 $(top_srcdir)/scripts/htmlman
+ rm -f $@.new
+ $(top_srcdir)/scripts/htmlman $< >$@.new
+ chmod 444 $@.new
+ mv -f $@.new $@
+
+manual.h: manual.html ${top_srcdir}/scripts/text2c
+ ${top_srcdir}/scripts/text2c manual manual.html > $@.tmp
+ mv $@.tmp $@
EXTRA_DIST=disobedience.rc
void login_box(void);
+/* Help */
+
+void popup_help(void);
+
/* RTP */
int rtp_running(void);
--- /dev/null
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 2007 Richard Kettlewell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include "disobedience.h"
+#include "table.h"
+#include "html.h"
+#include "manual.h"
+
+VECTOR_TYPE(markstack, GtkTextMark *, xrealloc);
+
+/** @brief Known tag type */
+struct tag {
+ /** @brief HTML tag name */
+ const char *name;
+
+ /** @brief Called to set up the tag */
+ void (*init)(GtkTextTag *tag);
+
+ /** @brief GTK+ tag object */
+ GtkTextTag *tag;
+};
+
+static void init_bold(GtkTextTag *tag) {
+ g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_BOLD, (char *)0);
+}
+
+static void init_italic(GtkTextTag *tag) {
+ g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, (char *)0);
+}
+
+/** @brief Table of known tags
+ *
+ * Keep in alphabetical order
+ */
+static struct tag tags[] = {
+ { "b", init_bold, 0 },
+ { "i", init_italic, 0 }
+};
+
+/** @brief Number of known tags */
+#define NTAGS (sizeof tags / sizeof *tags)
+
+/** @brief State structure for insert_html() */
+struct state {
+ /** @brief The buffer to insert into */
+ GtkTextBuffer *buffer;
+
+ /** @brief True if we are inside <body> */
+ int body;
+
+ /** @brief Stack of marks corresponding to tags */
+ struct markstack marks[1];
+
+};
+
+/** @brief Called for an open tag */
+static void html_open(const char *tag,
+ hash attribute((unused)) *attrs,
+ void *u) {
+ struct state *const s = u;
+ GtkTextIter iter[1];
+
+ if(!strcmp(tag, "body"))
+ s->body = 1;
+ if(!s->body)
+ return;
+ /* push a mark for the start of the region */
+ gtk_text_buffer_get_iter_at_mark(s->buffer, iter,
+ gtk_text_buffer_get_insert(s->buffer));
+ markstack_append(s->marks,
+ gtk_text_buffer_create_mark(s->buffer,
+ 0/* mark_name */,
+ iter,
+ TRUE/*left_gravity*/));
+}
+
+/** @brief Called for a close tag */
+static void html_close(const char *tag,
+ void *u) {
+ struct state *const s = u;
+ GtkTextIter start[1], end[1];
+ int n;
+
+ if(!strcmp(tag, "body"))
+ s->body = 0;
+ if(!s->body)
+ return;
+ /* see if this is a known tag */
+ if((n = TABLE_FIND(tags, struct tag, name, tag)) < 0)
+ return;
+ /* pop the mark at the start of the region */
+ assert(s->marks->nvec > 0);
+ gtk_text_buffer_get_iter_at_mark(s->buffer, start,
+ s->marks->vec[--s->marks->nvec]);
+ gtk_text_buffer_get_iter_at_mark(s->buffer, end,
+ gtk_text_buffer_get_insert(s->buffer));
+ /* apply a tag */
+ gtk_text_buffer_apply_tag(s->buffer, tags[n].tag, start, end);
+ /* don't need the start mark any more */
+ gtk_text_buffer_delete_mark(s->buffer, s->marks->vec[s->marks->nvec]);
+}
+
+/** @brief Called for text */
+static void html_text(const char *text,
+ void *u) {
+ struct state *const s = u;
+
+ /* ignore header */
+ if(!s->body)
+ return;
+ gtk_text_buffer_insert_at_cursor(s->buffer, text, strlen(text));
+}
+
+/** @brief Callbacks for insert_html() */
+static const struct html_parser_callbacks insert_html_callbacks = {
+ html_open,
+ html_close,
+ html_text
+};
+
+/** @brief Insert @p html into @p buffer at cursor */
+static void insert_html(GtkTextBuffer *buffer,
+ const char *html) {
+ struct state s[1];
+ size_t n;
+ GtkTextTagTable *tagtable;
+
+ memset(s, 0, sizeof *s);
+ s->buffer = buffer;
+ markstack_init(s->marks);
+ /* initialize tags */
+ if(!tags[0].tag)
+ for(n = 0; n < NTAGS; ++n)
+ tags[n].init(tags[n].tag = gtk_text_tag_new(0));
+ /* add tags to this buffer */
+ tagtable = gtk_text_buffer_get_tag_table(s->buffer);
+ for(n = 0; n < NTAGS; ++n)
+ gtk_text_tag_table_add(tagtable, tags[n].tag);
+ /* convert the input */
+ html_parse(&insert_html_callbacks, html, s);
+}
+
+static GtkTextBuffer *html_buffer(const char *html) {
+ GtkTextBuffer *buffer = gtk_text_buffer_new(NULL);
+
+ insert_html(buffer, html);
+ return buffer;
+}
+
+static GtkWidget *help_window;
+
+void popup_help(void) {
+ GtkWidget *view;
+
+ if(help_window) {
+ gtk_window_present(GTK_WINDOW(help_window));
+ return;
+ }
+ help_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ g_signal_connect(help_window, "destroy",
+ G_CALLBACK(gtk_widget_destroyed), &help_window);
+ gtk_window_set_title(GTK_WINDOW(help_window), "Disobedience Manual");
+ view = gtk_text_view_new_with_buffer(html_buffer(manual));
+ gtk_container_add(GTK_CONTAINER(help_window),
+ scroll_widget(view,
+ "help"));
+ gtk_widget_show_all(help_window);
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
0);
}
+static void manual_popup(gpointer attribute((unused)) callback_data,
+ guint attribute((unused)) callback_action,
+ GtkWidget attribute((unused)) *menu_item) {
+ D(("manual_popup"));
+
+ popup_help();
+}
+
/** @brief Callde when version arrives, displays about... popup */
static void about_popup_got_version(void attribute((unused)) *v,
const char *value) {
(char *)"<Branch>", /* item_type */
0 /* extra_data */
},
+ {
+ (char *)"/Help/Manual page", /* path */
+ 0, /* accelerator */
+ manual_popup, /* callback */
+ 0, /* callback_action */
+ 0, /* item_type */
+ 0 /* extra_data */
+ },
{
(char *)"/Help/About DisOrder", /* path */
0, /* accelerator */
$(HTMLMAN) : %.html : % $(top_srcdir)/scripts/htmlman
rm -f $@.new
- $(top_srcdir)/scripts/htmlman $< >$@.new
+ $(top_srcdir)/scripts/htmlman -stdhead $< >$@.new
chmod 444 $@.new
mv -f $@.new $@
hash.c hash.h \
heap.h \
hex.c hex.h \
+ html.c html.h \
ifreq.c ifreq.h \
inputline.c inputline.h \
kvp.c kvp.h \
return d.vec;
}
+/** @brief Convert one UCS-4 character to UTF-8
+ * @param c Character to convert
+ * @param d Dynamic string to append UTF-8 sequence to
+ * @return 0 on success, -1 on error
+ */
+int one_ucs42utf8(uint32_t c, struct dynstr *d) {
+ if(c < 0x80)
+ dynstr_append(d, c);
+ else if(c < 0x800) {
+ dynstr_append(d, 0xC0 | (c >> 6));
+ dynstr_append(d, 0x80 | (c & 0x3F));
+ } else if(c < 0x10000) {
+ dynstr_append(d, 0xE0 | (c >> 12));
+ dynstr_append(d, 0x80 | ((c >> 6) & 0x3F));
+ dynstr_append(d, 0x80 | (c & 0x3F));
+ } else if(c < 0x110000) {
+ dynstr_append(d, 0xF0 | (c >> 18));
+ dynstr_append(d, 0x80 | ((c >> 12) & 0x3F));
+ dynstr_append(d, 0x80 | ((c >> 6) & 0x3F));
+ dynstr_append(d, 0x80 | (c & 0x3F));
+ } else {
+ error(0, "invalid UCS-4 character %#"PRIx32, c);
+ return -1;
+ }
+ return 0;
+}
+
/** @brief Convert UCS-4 to UTF-8
* @param u Pointer to 0-terminated UCS-4 string
* @return Pointer to 0-terminated UTF-8 string
dynstr_init(&d);
while((c = *u++)) {
- if(c < 0x80)
- dynstr_append(&d, c);
- else if(c < 0x800) {
- dynstr_append(&d, 0xC0 | (c >> 6));
- dynstr_append(&d, 0x80 | (c & 0x3F));
- } else if(c < 0x10000) {
- dynstr_append(&d, 0xE0 | (c >> 12));
- dynstr_append(&d, 0x80 | ((c >> 6) & 0x3F));
- dynstr_append(&d, 0x80 | (c & 0x3F));
- } else if(c < 0x110000) {
- dynstr_append(&d, 0xF0 | (c >> 18));
- dynstr_append(&d, 0x80 | ((c >> 12) & 0x3F));
- dynstr_append(&d, 0x80 | ((c >> 6) & 0x3F));
- dynstr_append(&d, 0x80 | (c & 0x3F));
- } else {
- error(0, "invalid UCS-4 character");
+ if(one_ucs42utf8(c, &d))
return 0;
- }
}
dynstr_terminate(&d);
return d.vec;
#ifndef CHARSET_H
#define CHARSET_H
+struct dynstr;
+
/* Character encoding conversion routines */
+int one_ucs42utf8(uint32_t c, struct dynstr *d);
+
uint32_t *utf82ucs4(const char *mb);
char *ucs42utf8(const uint32_t *u);
char *mb2utf8(const char *mb);
--- /dev/null
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 2007 Richard Kettlewell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+/** @file lib/html.c
+ * @brief Noddy HTML parser
+ */
+
+#include <config.h>
+#include "types.h"
+
+#include <string.h>
+#include <ctype.h>
+#include <stddef.h>
+
+#include "hash.h"
+#include "html.h"
+#include "mem.h"
+#include "log.h"
+#include "vector.h"
+#include "charset.h"
+#include "table.h"
+
+/** @brief Entity table type */
+struct entity {
+ const char *name;
+ uint32_t value;
+};
+
+/** @brief Known entities
+ *
+ * We only support the entities that turn up in the HTML files we
+ * actually care about.
+ *
+ * Keep in alphabetical order.
+ */
+static const struct entity entities[] = {
+ { "amp", '&' },
+ { "gt", '>' },
+ { "lt", '<' }
+};
+
+/** @brief Skip whitespace */
+static const char *skipwhite(const char *input) {
+ while(*input && isspace((unsigned char)*input))
+ ++input;
+ return input;
+}
+
+/** @brief Parse an entity */
+static const char *parse_entity(const char *input,
+ uint32_t *entityp) {
+ input = skipwhite(input);
+ if(*input == '#') {
+ input = skipwhite(input + 1);
+ if(*input == 'x')
+ *entityp = strtoul(skipwhite(input + 1), (char **)&input, 16);
+ else
+ *entityp = strtoul(input, (char **)&input, 10);
+ } else {
+ struct dynstr name[1];
+ int n;
+
+ dynstr_init(name);
+ while(isalnum((unsigned char)*input))
+ dynstr_append(name, tolower((unsigned char)*input++));
+ dynstr_terminate(name);
+ if((n = TABLE_FIND(entities, struct entity, name, name->vec)) < 0) {
+ error(0, "unknown entity '%s'", name->vec);
+ *entityp = '?';
+ } else
+ *entityp = entities[n].value;
+ }
+ input = skipwhite(input);
+ if(*input == ';')
+ ++input;
+ return input;
+}
+
+/** @brief Parse one character or entity and append it to a @ref dynstr */
+static const char *parse_one(const char *input, struct dynstr *d) {
+ if(*input == '&') {
+ uint32_t c;
+ input = parse_entity(input + 1, &c);
+ if(one_ucs42utf8(c, d))
+ dynstr_append(d, '?'); /* U+FFFD might be a better choice */
+ } else
+ dynstr_append(d, *input++);
+ return input;
+}
+
+/** @brief Too-stupid-to-live HTML parser
+ * @param callbacks Parser callbacks
+ * @param input HTML document
+ * @param u User data pointer
+ * @return 0 on success, -1 on error
+ */
+int html_parse(const struct html_parser_callbacks *callbacks,
+ const char *input,
+ void *u) {
+ struct dynstr text[1];
+
+ dynstr_init(text);
+ while(*input) {
+ if(*input == '<') {
+ struct dynstr tag[1];
+ hash *attrs;
+
+ /* flush collected text */
+ if(text->nvec) {
+ dynstr_terminate(text);
+ callbacks->text(text->vec, u);
+ text->nvec = 0;
+ }
+ dynstr_init(tag);
+ input = skipwhite(input + 1);
+ /* see if it's an open or close tag */
+ if(*input == '/') {
+ input = skipwhite(input + 1);
+ attrs = 0;
+ } else
+ attrs = hash_new(sizeof(char *));
+ /* gather tag */
+ while(isalnum((unsigned char)*input))
+ dynstr_append(tag, tolower((unsigned char)*input++));
+ dynstr_terminate(tag);
+ input = skipwhite(input);
+ if(attrs) {
+ /* gather attributes */
+ while(*input && *input != '>') {
+ struct dynstr name[1], value[1];
+
+ dynstr_init(name);
+ dynstr_init(value);
+ /* attribute name */
+ while(isalnum((unsigned char)*input))
+ dynstr_append(name, tolower((unsigned char)*input++));
+ dynstr_terminate(name);
+ input = skipwhite(input);
+ if(*input == '=') {
+ /* attribute value */
+ input = skipwhite(input + 1);
+ if(*input == '"' || *input == '\'') {
+ /* quoted value */
+ const int q = *input++;
+ while(*input && *input != q)
+ input = parse_one(input, value);
+ if(*input == q)
+ ++input;
+ } else {
+ /* unquoted value */
+ while(*input && *input != '>' && !isspace((unsigned char)*input))
+ input = parse_one(input, value);
+ }
+ dynstr_terminate(value);
+ }
+ /* stash the value */
+ hash_add(attrs, name->vec, value->vec, HASH_INSERT_OR_REPLACE);
+ input = skipwhite(input);
+ }
+ }
+ if(*input != '>') {
+ error(0, "unterminated tag %s", tag->vec);
+ return -1;
+ }
+ ++input;
+ if(attrs)
+ callbacks->open(tag->vec, attrs, u);
+ else
+ callbacks->close(tag->vec, u);
+ } else
+ input = parse_one(input, text);
+ }
+ /* flush any trailing text */
+ if(text->nvec) {
+ dynstr_terminate(text);
+ callbacks->text(text->vec, u);
+ text->nvec = 0;
+ }
+ return 0;
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
--- /dev/null
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 2007 Richard Kettlewell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+/** @file lib/html.c
+ * @brief Noddy HTML parser
+ */
+
+#ifndef HTML_H
+#define HTML_H
+
+/** @brief HTML parser callbacks */
+struct html_parser_callbacks {
+ /** @brief Called for an open tag
+ * @param tag Name of tag, normalized to lower case
+ * @param attrs Hash containing attributes
+ * @param u User data pointer
+ */
+ void (*open)(const char *tag,
+ hash *attrs,
+ void *u);
+
+ /** @brief Called for a close tag
+ * @param tag Name of tag, normalized to lower case
+ * @param u User data pointer
+ */
+ void (*close)(const char *tag,
+ void *u);
+
+ /** @brief Called for text
+ * @param text Pointer to text
+ * @param u User data pointer
+ */
+ void (*text)(const char *text,
+ void *u);
+};
+
+int html_parse(const struct html_parser_callbacks *callbacks,
+ const char *input,
+ void *u);
+
+#endif /* HTML_H */
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
set -e
+stdhead=false
+
+while test $# -gt 0; do
+ case "$1" in
+ -stdhead )
+ stdhead=true
+ ;;
+ -* )
+ echo >&2 "ERROR: unknown option $1"
+ exit 1
+ ;;
+ * )
+ break
+ esac
+ shift
+done
+
title=$(basename $1)
-cat <<EOF
-<html>
- <head>
-@include{stdhead}@
- <title>$title</title>
- </head>
- <body>
-@include{@label{menu}@}@
-EOF
+echo "<html>"
+echo " <head>"
+if $stdhead; then
+ echo "@include{stdhead}@"
+fi
+echo " <title>$title</title>"
+echo " </head>"
+echo " <body>"
+if $stdhead; then
+ echo "@include{@label{menu}@}@"
+fi
printf " <pre class=manpage>"
# this is kind of painful using only BREs
-nroff -man "$1" | sed 's/&/\&/g;
+nroff -man "$1" | sed \
+ '1d;$d;
+ s/&/\&/g;
s/</\</g;
s/>/\>/g;
s/@/\@/g;
s!_\b\(.\)!<i>\1</i>!g;
s!_\b\(&[#0-9a-z][0-9a-z]*;\)!<i>\1</i>!g;
s!</\([bi]\)><\1>!!g'
-cat <<EOF
-</pre>
-@include{@label{menu}@end}@
- </body>
-</html>
-EOF
+echo "</pre>"
+if $stdhead; then
+ echo "@include{@label{menu}@end}@"
+fi
+echo " </body>"
+echo "</html>"