mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
Merge 835572b97e
into 5ce3a9dd1d
This commit is contained in:
commit
7d2de0c95b
@ -127,7 +127,6 @@ typedef struct GncPluginPageReportPrivate
|
|||||||
/// the gnc_html abstraction this PluginPage contains
|
/// the gnc_html abstraction this PluginPage contains
|
||||||
// gnc_html *html;
|
// gnc_html *html;
|
||||||
GncHtml *html;
|
GncHtml *html;
|
||||||
gboolean webkit2;
|
|
||||||
|
|
||||||
/// the container the above HTML widget is in.
|
/// the container the above HTML widget is in.
|
||||||
GtkContainer *container;
|
GtkContainer *container;
|
||||||
@ -519,11 +518,6 @@ gnc_plugin_page_report_create_widget( GncPluginPage *page )
|
|||||||
report = GNC_PLUGIN_PAGE_REPORT(page);
|
report = GNC_PLUGIN_PAGE_REPORT(page);
|
||||||
priv = GNC_PLUGIN_PAGE_REPORT_GET_PRIVATE(report);
|
priv = GNC_PLUGIN_PAGE_REPORT_GET_PRIVATE(report);
|
||||||
|
|
||||||
#ifndef WEBKIT1
|
|
||||||
/* Hide the ExportPdf action for Webkit2 */
|
|
||||||
priv->webkit2 = TRUE;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
topLvl = gnc_ui_get_main_window (nullptr);
|
topLvl = gnc_ui_get_main_window (nullptr);
|
||||||
// priv->html = gnc_html_new( topLvl );
|
// priv->html = gnc_html_new( topLvl );
|
||||||
priv->html = gnc_html_factory_create_html();
|
priv->html = gnc_html_factory_create_html();
|
||||||
@ -1249,8 +1243,6 @@ gnc_plugin_page_report_menu_update (GncPluginPage *plugin_page,
|
|||||||
static void
|
static void
|
||||||
gnc_plugin_page_report_menu_updates (GncPluginPage *plugin_page)
|
gnc_plugin_page_report_menu_updates (GncPluginPage *plugin_page)
|
||||||
{
|
{
|
||||||
GncPluginPageReportPrivate *priv;
|
|
||||||
GncPluginPageReport *report;
|
|
||||||
GncMainWindow *window;
|
GncMainWindow *window;
|
||||||
action_toolbar_labels tooltip_list[3];
|
action_toolbar_labels tooltip_list[3];
|
||||||
GAction *action;
|
GAction *action;
|
||||||
@ -1263,9 +1255,6 @@ gnc_plugin_page_report_menu_updates (GncPluginPage *plugin_page)
|
|||||||
_("Add the current report's configuration to the 'Reports->Saved Report Configurations' menu. "
|
_("Add the current report's configuration to the 'Reports->Saved Report Configurations' menu. "
|
||||||
"The report configuration will be saved in the file %s."), saved_reports_path);
|
"The report configuration will be saved in the file %s."), saved_reports_path);
|
||||||
|
|
||||||
report = GNC_PLUGIN_PAGE_REPORT(plugin_page);
|
|
||||||
priv = GNC_PLUGIN_PAGE_REPORT_GET_PRIVATE(report);
|
|
||||||
|
|
||||||
window = (GncMainWindow*)gnc_plugin_page_get_window (GNC_PLUGIN_PAGE(plugin_page));
|
window = (GncMainWindow*)gnc_plugin_page_get_window (GNC_PLUGIN_PAGE(plugin_page));
|
||||||
|
|
||||||
tooltip_list[0] = { "ReportSaveAction", N_("Save _Report Configuration"), report_save_str };
|
tooltip_list[0] = { "ReportSaveAction", N_("Save _Report Configuration"), report_save_str };
|
||||||
@ -1278,11 +1267,6 @@ gnc_plugin_page_report_menu_updates (GncPluginPage *plugin_page)
|
|||||||
action = gnc_main_window_find_action (window, "FilePrintAction");
|
action = gnc_main_window_find_action (window, "FilePrintAction");
|
||||||
g_simple_action_set_enabled (G_SIMPLE_ACTION(action), true);
|
g_simple_action_set_enabled (G_SIMPLE_ACTION(action), true);
|
||||||
|
|
||||||
if (priv->webkit2)
|
|
||||||
{
|
|
||||||
GtkWidget *pdf_item = gnc_main_window_menu_find_menu_item (window, "FilePrintPDFAction");
|
|
||||||
gtk_widget_hide (pdf_item);
|
|
||||||
}
|
|
||||||
g_free (saved_reports_path);
|
g_free (saved_reports_path);
|
||||||
g_free (report_save_str);
|
g_free (report_save_str);
|
||||||
g_free (report_saveas_str);
|
g_free (report_saveas_str);
|
||||||
@ -1299,7 +1283,6 @@ gnc_plugin_page_report_constr_init (GncPluginPageReport *plugin_page, gint repor
|
|||||||
DEBUG("property reportId=%d", reportId);
|
DEBUG("property reportId=%d", reportId);
|
||||||
priv = GNC_PLUGIN_PAGE_REPORT_GET_PRIVATE(plugin_page);
|
priv = GNC_PLUGIN_PAGE_REPORT_GET_PRIVATE(plugin_page);
|
||||||
priv->reportId = reportId;
|
priv->reportId = reportId;
|
||||||
priv->webkit2 = FALSE;
|
|
||||||
|
|
||||||
gnc_plugin_page_report_setup( GNC_PLUGIN_PAGE(plugin_page));
|
gnc_plugin_page_report_setup( GNC_PLUGIN_PAGE(plugin_page));
|
||||||
|
|
||||||
@ -1991,11 +1974,7 @@ gnc_plugin_page_report_print_cb (GSimpleAction *simple,
|
|||||||
|
|
||||||
//g_warning("Setting job name=%s", job_name);
|
//g_warning("Setting job name=%s", job_name);
|
||||||
|
|
||||||
#ifdef WEBKIT1
|
|
||||||
gnc_html_print (priv->html, job_name, FALSE);
|
|
||||||
#else
|
|
||||||
gnc_html_print (priv->html, job_name);
|
gnc_html_print (priv->html, job_name);
|
||||||
#endif
|
|
||||||
|
|
||||||
g_free (job_name);
|
g_free (job_name);
|
||||||
}
|
}
|
||||||
@ -2037,11 +2016,7 @@ gnc_plugin_page_report_exportpdf_cb (GSimpleAction *simple,
|
|||||||
|
|
||||||
//g_warning("Setting job name=%s", job_name);
|
//g_warning("Setting job name=%s", job_name);
|
||||||
|
|
||||||
#ifdef WEBKIT1
|
|
||||||
gnc_html_print (priv->html, job_name, TRUE);
|
|
||||||
#else
|
|
||||||
gnc_html_print (priv->html, job_name);
|
gnc_html_print (priv->html, job_name);
|
||||||
#endif
|
|
||||||
|
|
||||||
if (owner)
|
if (owner)
|
||||||
{
|
{
|
||||||
|
@ -5,8 +5,10 @@ set (html_HEADERS
|
|||||||
gnc-html-p.h
|
gnc-html-p.h
|
||||||
gnc-html-factory.h
|
gnc-html-factory.h
|
||||||
gnc-html-extras.h
|
gnc-html-extras.h
|
||||||
gnc-html-webkit-p.h
|
gnc-html-browser-p.h
|
||||||
gnc-html-webkit.h
|
gnc-html-browser.h
|
||||||
|
gnc-ws-server.h
|
||||||
|
gnc-ws-protocol.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# Command to generate the swig-gnc-html.c wrapper file
|
# Command to generate the swig-gnc-html.c wrapper file
|
||||||
@ -19,20 +21,11 @@ set (html_SOURCES
|
|||||||
gnc-html.c
|
gnc-html.c
|
||||||
gnc-html-history.c
|
gnc-html-history.c
|
||||||
gnc-html-factory.c
|
gnc-html-factory.c
|
||||||
|
gnc-html-browser.c
|
||||||
|
gnc-ws-server.c
|
||||||
|
gnc-ws-protocol.c
|
||||||
)
|
)
|
||||||
|
|
||||||
if (WEBKIT1)
|
|
||||||
list(APPEND html_HEADERS gnc-html-webkit1.h)
|
|
||||||
list(APPEND html_SOURCES gnc-html-webkit1.c)
|
|
||||||
set(html_EXTRA_DIST gnc-html-webkit2.h gnc-html-webkit2.c)
|
|
||||||
else ()
|
|
||||||
list(APPEND html_HEADERS gnc-html-webkit2.h)
|
|
||||||
list(APPEND html_SOURCES gnc-html-webkit2.c)
|
|
||||||
set(html_EXTRA_DIST gnc-html-webkit1.h gnc-html-webkit1.c)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
set (gnc_html_SCHEME html.scm)
|
set (gnc_html_SCHEME html.scm)
|
||||||
|
|
||||||
set(GUILE_OUTPUT_DIR gnucash)
|
set(GUILE_OUTPUT_DIR gnucash)
|
||||||
@ -57,7 +50,6 @@ target_link_libraries(gnc-html
|
|||||||
gnc-engine
|
gnc-engine
|
||||||
gnc-gnome-utils
|
gnc-gnome-utils
|
||||||
PkgConfig::GTK3
|
PkgConfig::GTK3
|
||||||
PkgConfig::WEBKIT
|
|
||||||
${GUILE_LDFLAGS})
|
${GUILE_LDFLAGS})
|
||||||
|
|
||||||
target_compile_definitions(gnc-html PRIVATE -DG_LOG_DOMAIN=\"gnc.html\")
|
target_compile_definitions(gnc-html PRIVATE -DG_LOG_DOMAIN=\"gnc.html\")
|
||||||
|
40
gnucash/html/gnc-html-browser-p.h
Normal file
40
gnucash/html/gnc-html-browser-p.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/********************************************************************
|
||||||
|
* gnc-html-browser-p.h -- display html with gnc special tags *
|
||||||
|
* Copyright (C) 2024 Bob Fewell *
|
||||||
|
* *
|
||||||
|
* 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, contact: *
|
||||||
|
* *
|
||||||
|
* Free Software Foundation Voice: +1-617-542-5942 *
|
||||||
|
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
||||||
|
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
||||||
|
\********************************************************************/
|
||||||
|
|
||||||
|
#ifndef GNC_HTML_BROWSER_P_H
|
||||||
|
#define GNC_HTML_BROWSER_P_H
|
||||||
|
|
||||||
|
#include "gnc-html-p.h"
|
||||||
|
|
||||||
|
struct _GncHtmlBrowserPrivate
|
||||||
|
{
|
||||||
|
struct _GncHtmlPrivate base;
|
||||||
|
|
||||||
|
GtkWidget *web_view;
|
||||||
|
gchar* html_string; /* html string being displayed */
|
||||||
|
|
||||||
|
GncWsServer *gws;
|
||||||
|
|
||||||
|
const gchar *file_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
1046
gnucash/html/gnc-html-browser.c
Normal file
1046
gnucash/html/gnc-html-browser.c
Normal file
File diff suppressed because it is too large
Load Diff
64
gnucash/html/gnc-html-browser.h
Normal file
64
gnucash/html/gnc-html-browser.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/********************************************************************
|
||||||
|
* gnc-html-browser.h -- display html with gnc special tags *
|
||||||
|
* Copyright (C) 2024 Bob Fewell *
|
||||||
|
* *
|
||||||
|
* 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, contact: *
|
||||||
|
* *
|
||||||
|
* Free Software Foundation Voice: +1-617-542-5942 *
|
||||||
|
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
||||||
|
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
||||||
|
\********************************************************************/
|
||||||
|
|
||||||
|
#ifndef GNC_HTML_BROWSER_H
|
||||||
|
#define GNC_HTML_BROWSER_H
|
||||||
|
|
||||||
|
#include <glib-object.h>
|
||||||
|
#include "gnc-html.h"
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define GNC_TYPE_HTML_BROWSER (gnc_html_browser_get_type())
|
||||||
|
#define GNC_HTML_BROWSER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GNC_TYPE_HTML_BROWSER, GncHtmlBrowser))
|
||||||
|
#define GNC_HTML_BROWSER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GNC_TYPE_HTML_BROWSER, GncHtmlBrowserClass))
|
||||||
|
#define GNC_IS_HTML_BROWSER(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), GNC_TYPE_HTML_BROWSER))
|
||||||
|
#define GNC_IS_HTML_BROWSER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), GNC_TYPE_HTML_BROWSER))
|
||||||
|
#define GNC_HTML_BROWSER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GNC_TYPE_HTML_BROWSER, GncHtmlBrowserClass))
|
||||||
|
|
||||||
|
typedef struct _GncHtmlBrowser GncHtmlBrowser;
|
||||||
|
typedef struct _GncHtmlBrowserClass GncHtmlBrowserClass;
|
||||||
|
typedef struct _GncHtmlBrowserPrivate GncHtmlBrowserPrivate;
|
||||||
|
|
||||||
|
/** Key for saving the PDF-export directory in the print settings */
|
||||||
|
#define GNC_GTK_PRINT_SETTINGS_EXPORT_DIR "gnc-pdf-export-directory"
|
||||||
|
|
||||||
|
struct _GncHtmlBrowser
|
||||||
|
{
|
||||||
|
GncHtml parent_instance;
|
||||||
|
|
||||||
|
/*< private >*/
|
||||||
|
GncHtmlBrowserPrivate* priv;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _GncHtmlBrowserClass
|
||||||
|
{
|
||||||
|
GncHtmlClass parent_class;
|
||||||
|
};
|
||||||
|
|
||||||
|
GType gnc_html_browser_get_type (void);
|
||||||
|
|
||||||
|
GncHtml* gnc_html_browser_new (void);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif
|
@ -26,7 +26,7 @@
|
|||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
#include "gnc-html.h"
|
#include "gnc-html.h"
|
||||||
#include "gnc-html-webkit.h"
|
#include "gnc-html-browser.h"
|
||||||
#include "qoflog.h"
|
#include "qoflog.h"
|
||||||
#include "gnc-engine.h"
|
#include "gnc-engine.h"
|
||||||
|
|
||||||
@ -35,13 +35,13 @@
|
|||||||
/* indicates the debugging module that this .o belongs to. */
|
/* indicates the debugging module that this .o belongs to. */
|
||||||
G_GNUC_UNUSED static QofLogModule log_module = GNC_MOD_HTML;
|
G_GNUC_UNUSED static QofLogModule log_module = GNC_MOD_HTML;
|
||||||
|
|
||||||
GncHtml* gnc_html_factory_create_html( void )
|
GncHtml* gnc_html_factory_create_html (void)
|
||||||
{
|
{
|
||||||
return gnc_html_webkit_new();
|
return gnc_html_browser_new ();
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
gnc_html_engine_supports_css( void )
|
gnc_html_engine_supports_css (void)
|
||||||
{
|
{
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
@ -528,13 +528,9 @@ gnc_html_export_to_file( GncHtml* self, const gchar* filepath )
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#ifdef WEBKIT1
|
|
||||||
void
|
|
||||||
gnc_html_print (GncHtml* self, const char *jobname, gboolean export_pdf)
|
|
||||||
#else
|
|
||||||
void
|
void
|
||||||
gnc_html_print (GncHtml* self, const char *jobname)
|
gnc_html_print (GncHtml* self, const char *jobname)
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
g_return_if_fail( self != NULL );
|
g_return_if_fail( self != NULL );
|
||||||
g_return_if_fail( jobname != NULL );
|
g_return_if_fail( jobname != NULL );
|
||||||
@ -542,11 +538,7 @@ gnc_html_print (GncHtml* self, const char *jobname)
|
|||||||
|
|
||||||
if ( GNC_HTML_GET_CLASS(self)->print != NULL )
|
if ( GNC_HTML_GET_CLASS(self)->print != NULL )
|
||||||
{
|
{
|
||||||
#ifdef WEBKIT1
|
|
||||||
GNC_HTML_GET_CLASS(self)->print (self, jobname, export_pdf);
|
|
||||||
#else
|
|
||||||
GNC_HTML_GET_CLASS(self)->print (self, jobname);
|
GNC_HTML_GET_CLASS(self)->print (self, jobname);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -589,17 +581,13 @@ gnc_html_get_webview( GncHtml* self )
|
|||||||
|
|
||||||
if (sw_list) // the scroll window has only one child
|
if (sw_list) // the scroll window has only one child
|
||||||
{
|
{
|
||||||
#ifdef WEBKIT1
|
|
||||||
webview = sw_list->data;
|
|
||||||
#else
|
|
||||||
GList *vp_list = gtk_container_get_children (GTK_CONTAINER(sw_list->data));
|
GList *vp_list = gtk_container_get_children (GTK_CONTAINER(sw_list->data));
|
||||||
|
|
||||||
if (vp_list) // the viewport has only one child
|
if (vp_list) // the viewport has only one child
|
||||||
{
|
{
|
||||||
webview = vp_list->data;
|
webview = vp_list->data;
|
||||||
g_list_free (vp_list);
|
g_list_free (vp_list);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
g_list_free (sw_list);
|
g_list_free (sw_list);
|
||||||
return webview;
|
return webview;
|
||||||
|
@ -138,11 +138,7 @@ struct _GncHtmlClass
|
|||||||
void (*reload)( GncHtml* html, gboolean force_rebuild );
|
void (*reload)( GncHtml* html, gboolean force_rebuild );
|
||||||
void (*copy_to_clipboard)( GncHtml* html );
|
void (*copy_to_clipboard)( GncHtml* html );
|
||||||
gboolean (*export_to_file)( GncHtml* html, const gchar* file );
|
gboolean (*export_to_file)( GncHtml* html, const gchar* file );
|
||||||
#ifdef WEBKIT1
|
|
||||||
void (*print) (GncHtml* html, const gchar* jobname, gboolean export_pdf);
|
|
||||||
#else
|
|
||||||
void (*print) (GncHtml* html, const gchar* jobname);
|
void (*print) (GncHtml* html, const gchar* jobname);
|
||||||
#endif
|
|
||||||
void (*cancel)( GncHtml* html );
|
void (*cancel)( GncHtml* html );
|
||||||
URLType (*parse_url)( GncHtml* html, const gchar* url,
|
URLType (*parse_url)( GncHtml* html, const gchar* url,
|
||||||
gchar** url_location, gchar** url_label );
|
gchar** url_location, gchar** url_label );
|
||||||
@ -203,25 +199,13 @@ void gnc_html_copy_to_clipboard( GncHtml* html );
|
|||||||
*/
|
*/
|
||||||
gboolean gnc_html_export_to_file( GncHtml* html, const gchar* filename );
|
gboolean gnc_html_export_to_file( GncHtml* html, const gchar* filename );
|
||||||
|
|
||||||
#ifdef WEBKIT1
|
|
||||||
/**
|
|
||||||
* Prints the report.
|
|
||||||
*
|
|
||||||
* @param html GncHtml object
|
|
||||||
* @param jobname A jobname for identifying the print job or to provide
|
|
||||||
* an output filename.
|
|
||||||
* @param export_pdf If TRUE write a PDF file using the jobname for a
|
|
||||||
* filename; otherwise put up a print dialog.
|
|
||||||
*/
|
|
||||||
void gnc_html_print (GncHtml* html, const char* jobname, gboolean export_pdf);
|
|
||||||
#else
|
|
||||||
/**
|
/**
|
||||||
* Prints the report.
|
* Prints the report.
|
||||||
*
|
*
|
||||||
* @param html GncHtml object
|
* @param html GncHtml object
|
||||||
*/
|
*/
|
||||||
void gnc_html_print (GncHtml* html, const char* jobname);
|
void gnc_html_print (GncHtml* html, const char* jobname);
|
||||||
#endif
|
|
||||||
/**
|
/**
|
||||||
* Cancels the current operation
|
* Cancels the current operation
|
||||||
*
|
*
|
||||||
|
338
gnucash/html/gnc-ws-protocol.c
Normal file
338
gnucash/html/gnc-ws-protocol.c
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
/********************************************************************
|
||||||
|
* gnc-ws-protocol.c -- basic websocket server protocol *
|
||||||
|
* Copyright (C) 2024 Bob Fewell *
|
||||||
|
* *
|
||||||
|
* 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, contact: *
|
||||||
|
* *
|
||||||
|
* Free Software Foundation Voice: +1-617-542-5942 *
|
||||||
|
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
||||||
|
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
||||||
|
********************************************************************/
|
||||||
|
|
||||||
|
#include <config.h>
|
||||||
|
#include <glib/gi18n.h>
|
||||||
|
#include <glib/gstdio.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
#include "gnc-ws-protocol.h"
|
||||||
|
|
||||||
|
#define WS_KEY "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||||
|
|
||||||
|
#define WS_HS_ACCEPT "HTTP/1.1 101 Switching Protocols\r\n"\
|
||||||
|
"Upgrade: websocket\r\n"\
|
||||||
|
"Connection: Upgrade\r\n"\
|
||||||
|
"Sec-WebSocket-Accept: "
|
||||||
|
|
||||||
|
#ifdef skip
|
||||||
|
static void
|
||||||
|
print_hex (const gchar *s)
|
||||||
|
{
|
||||||
|
while (*s)
|
||||||
|
g_printf("%02x", (unsigned int) *s++);
|
||||||
|
g_printf("\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef skip
|
||||||
|
static void
|
||||||
|
print_bytes (GBytes *bytes_in)
|
||||||
|
{
|
||||||
|
gsize length;
|
||||||
|
const guint8 *array = g_bytes_get_data (bytes_in, &length);
|
||||||
|
GString *printable;
|
||||||
|
guint i = 0;
|
||||||
|
|
||||||
|
printable = g_string_new ("[");
|
||||||
|
|
||||||
|
while (i < length)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
g_string_append_c (printable, ' ');
|
||||||
|
g_string_append_printf (printable, "%02x", array[i++]);
|
||||||
|
}
|
||||||
|
g_string_append_c (printable, ']');
|
||||||
|
|
||||||
|
gchar *test = g_string_free (printable, FALSE);
|
||||||
|
|
||||||
|
g_print("Print bytes: '%s' %" G_GUINT64_FORMAT "\n", test, length);
|
||||||
|
|
||||||
|
g_free (test);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
static gchar*
|
||||||
|
find_header_text (gchar **header, const gchar *find_text)
|
||||||
|
{
|
||||||
|
gchar *ret_text = NULL;
|
||||||
|
guint parts = g_strv_length (header);
|
||||||
|
|
||||||
|
for (guint i = 0; i < parts; i++)
|
||||||
|
{
|
||||||
|
if (g_strstr_len (header[i], -1, find_text))
|
||||||
|
{
|
||||||
|
ret_text = g_strdup (header[i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define BUFFER_LEN 200 // only dealing with max message of 125
|
||||||
|
|
||||||
|
static GBytes *
|
||||||
|
make_frame (guint8 opcode, const gchar *message)
|
||||||
|
{
|
||||||
|
guint8 first_byte = (0x80 | opcode);
|
||||||
|
guint8 msg_len = strlen (message);
|
||||||
|
|
||||||
|
if (msg_len > 125)
|
||||||
|
{
|
||||||
|
g_print("## Not supporting messages lonmger than 125 characters ##\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
guint8 header[2];
|
||||||
|
|
||||||
|
header[0] = first_byte;
|
||||||
|
header[1] = msg_len;
|
||||||
|
|
||||||
|
static guint8 array_buf[BUFFER_LEN];
|
||||||
|
|
||||||
|
memset (array_buf, 0, BUFFER_LEN);
|
||||||
|
memcpy (array_buf, header, 2);
|
||||||
|
memcpy (array_buf + 2, message, msg_len);
|
||||||
|
|
||||||
|
GBytes *test = g_bytes_new (array_buf, msg_len + 2);
|
||||||
|
|
||||||
|
return test;
|
||||||
|
}
|
||||||
|
|
||||||
|
GBytes *
|
||||||
|
gnc_ws_send_message (const gchar *message)
|
||||||
|
{
|
||||||
|
return make_frame (OPCODE_TEXT, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
parse_frame (GBytes *bytes_in, gint *opcode, GBytes **bytes_out)
|
||||||
|
{
|
||||||
|
gsize length;
|
||||||
|
const guint8 *array = g_bytes_get_data (bytes_in, &length);
|
||||||
|
|
||||||
|
const guint8 first_byte = array[0];
|
||||||
|
const guint8 second_byte = array[1];
|
||||||
|
|
||||||
|
gboolean fin = (first_byte >> 7) & 1;
|
||||||
|
gboolean rsv1 = (first_byte >> 6) & 1;
|
||||||
|
gboolean rsv2 = (first_byte >> 5) & 1;
|
||||||
|
gboolean rsv3 = (first_byte >> 4) & 1;
|
||||||
|
*opcode = first_byte & 0xf;
|
||||||
|
|
||||||
|
if (rsv1 || rsv2 || rsv3)
|
||||||
|
{
|
||||||
|
g_print("WebSocketError: Received frame with non-zero reserved bits\n");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fin == 0 && *opcode == OPCODE_CONTINUATION)
|
||||||
|
{
|
||||||
|
g_print("WebSocketError: Received new fragment frame with non-zero opcode\n");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean has_mask = (second_byte >> 7) & 1;
|
||||||
|
guint8 payload_len = second_byte & 0x7f;
|
||||||
|
gint offset = 2;
|
||||||
|
|
||||||
|
if ((*opcode > 0x7) && (payload_len > 125))
|
||||||
|
{
|
||||||
|
g_print("WebSocketError: Control frame payload cannot be larger than 125 bytes\n");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload_len > 125)
|
||||||
|
{
|
||||||
|
g_print("## Payload length greater than 125 bytes, not suported ##\n");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_mask)
|
||||||
|
{
|
||||||
|
guint8 masks[4];
|
||||||
|
gint i = 0;
|
||||||
|
|
||||||
|
masks[0] = array[offset];
|
||||||
|
masks[1] = array[offset + 1];
|
||||||
|
masks[2] = array[offset + 2];
|
||||||
|
masks[3] = array[offset + 3];
|
||||||
|
|
||||||
|
offset = offset + 4;
|
||||||
|
|
||||||
|
guint8 mypayload[length];
|
||||||
|
|
||||||
|
while ((i + offset) < length)
|
||||||
|
{
|
||||||
|
guint8 data = array[i + offset] ^ masks[i % 4];
|
||||||
|
mypayload[i] = data;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
*bytes_out = g_bytes_new (mypayload, length - offset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
*bytes_out = g_bytes_new_from_bytes (bytes_in, offset, length - offset);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
gchar *
|
||||||
|
gnc_ws_parse_bytes_in (GBytes *bytes_in, gint *opcode_out)
|
||||||
|
{
|
||||||
|
GBytes *bytes_out = NULL;
|
||||||
|
gint opcode;
|
||||||
|
gboolean ok = FALSE;
|
||||||
|
|
||||||
|
ok = parse_frame (bytes_in, &opcode, &bytes_out);
|
||||||
|
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
g_print("## Parsing bytes in failed ##\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*opcode_out = opcode;
|
||||||
|
|
||||||
|
if (opcode == OPCODE_TEXT)
|
||||||
|
{
|
||||||
|
gsize length;
|
||||||
|
const guint8 *array = g_bytes_get_data (bytes_out, &length);
|
||||||
|
GString *data_out = g_string_new (NULL);
|
||||||
|
gint i = 0;
|
||||||
|
|
||||||
|
while (i < length)
|
||||||
|
{
|
||||||
|
guint8 data = array[i];
|
||||||
|
|
||||||
|
g_string_append_printf (data_out, "%c", data);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return g_string_free (data_out, FALSE);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
gchar *
|
||||||
|
gnc_ws_make_handshake (const gchar *message, gchar **id)
|
||||||
|
{
|
||||||
|
gchar **message_split = g_strsplit (message, "\r\n", -1);
|
||||||
|
gchar *message_out = NULL;
|
||||||
|
gchar *key = NULL;
|
||||||
|
|
||||||
|
gchar *message_test = find_header_text (message_split, "GET");
|
||||||
|
|
||||||
|
if (!message_test)
|
||||||
|
{
|
||||||
|
g_print(" Error: No Get\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gchar *ptr = g_strrstr (message_test, " ");
|
||||||
|
|
||||||
|
if (ptr)
|
||||||
|
*id = g_utf8_substring (message_test, 5, ptr - message_test);
|
||||||
|
}
|
||||||
|
g_free (message_test);
|
||||||
|
|
||||||
|
message_test = find_header_text (message_split, "Upgrade");
|
||||||
|
if (!message_test)
|
||||||
|
{
|
||||||
|
g_print(" Error: No upgrade hdr\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
g_free (message_test);
|
||||||
|
|
||||||
|
message_test = find_header_text (message_split, "Sec-WebSocket-Version");
|
||||||
|
if (message_test)
|
||||||
|
{
|
||||||
|
if (!g_strstr_len (message_test , -1, "13"))
|
||||||
|
{
|
||||||
|
g_print(" Error: Unsupported version\n");
|
||||||
|
g_free (message_test);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_print(" Error: Did not find websocket version\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
g_free (message_test);
|
||||||
|
|
||||||
|
message_test = find_header_text (message_split, "Sec-WebSocket-Key");
|
||||||
|
|
||||||
|
if (message_test)
|
||||||
|
{
|
||||||
|
gchar *ptr = g_strstr_len (message_test , -1, ":");
|
||||||
|
|
||||||
|
if (ptr)
|
||||||
|
{
|
||||||
|
key = g_strstrip (g_strdup (ptr + 1));
|
||||||
|
|
||||||
|
gsize out_len;
|
||||||
|
guchar *decoded = g_base64_decode (key, &out_len);
|
||||||
|
|
||||||
|
if (out_len != 16)
|
||||||
|
{
|
||||||
|
g_print(" Error: Key wrong length\n");
|
||||||
|
g_free (message_test);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
g_free (decoded);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_print(" Error: Key not found\n");
|
||||||
|
g_free (message_test);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_print(" Error: Key not found\n");
|
||||||
|
g_free (message_test);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
g_free (message_test);
|
||||||
|
|
||||||
|
guint8 buf[100];
|
||||||
|
gsize bsize = 100;
|
||||||
|
|
||||||
|
GChecksum *cs = g_checksum_new (G_CHECKSUM_SHA1);
|
||||||
|
|
||||||
|
gchar *digest = g_strconcat (key, WS_KEY, NULL);
|
||||||
|
|
||||||
|
g_checksum_update (cs, (guint8*)digest, -1);
|
||||||
|
g_checksum_get_digest (cs, buf, &bsize);
|
||||||
|
gchar *str = g_base64_encode (buf, bsize);
|
||||||
|
message_out = g_strconcat (WS_HS_ACCEPT, str, "\r\n\r\n", NULL);
|
||||||
|
|
||||||
|
g_checksum_free (cs);
|
||||||
|
g_strfreev (message_split);
|
||||||
|
g_free (key);
|
||||||
|
|
||||||
|
return message_out;
|
||||||
|
}
|
45
gnucash/html/gnc-ws-protocol.h
Normal file
45
gnucash/html/gnc-ws-protocol.h
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/********************************************************************
|
||||||
|
* gnc-ws-protocol.h -- basic websocket server protocol *
|
||||||
|
* Copyright (C) 2024 Bob Fewell *
|
||||||
|
* *
|
||||||
|
* 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, contact: *
|
||||||
|
* *
|
||||||
|
* Free Software Foundation Voice: +1-617-542-5942 *
|
||||||
|
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
||||||
|
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
||||||
|
********************************************************************/
|
||||||
|
|
||||||
|
#ifndef WS_PROTOCOL_H
|
||||||
|
#define WS_PROTOCOL_H
|
||||||
|
|
||||||
|
#include <config.h>
|
||||||
|
|
||||||
|
#include <glib/gi18n.h>
|
||||||
|
#include <glib/gstdio.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
#define OPCODE_CONTINUATION 0x0
|
||||||
|
#define OPCODE_TEXT 0x1
|
||||||
|
#define OPCODE_BINARY 0x2
|
||||||
|
#define OPCODE_CLOSE 0x8
|
||||||
|
#define OPCODE_PING 0x9
|
||||||
|
#define OPCODE_PONG 0xa
|
||||||
|
|
||||||
|
gchar *gnc_ws_make_handshake (const gchar *message, gchar **id);
|
||||||
|
|
||||||
|
gchar *gnc_ws_parse_bytes_in (GBytes *bytes_in, gint *opcode);
|
||||||
|
|
||||||
|
GBytes *gnc_ws_send_message (const gchar *message);
|
||||||
|
|
||||||
|
#endif
|
399
gnucash/html/gnc-ws-server.c
Normal file
399
gnucash/html/gnc-ws-server.c
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
/********************************************************************
|
||||||
|
* gnc-ws-server.c -- basic websocket server *
|
||||||
|
* Copyright (C) 2024 Bob Fewell *
|
||||||
|
* *
|
||||||
|
* 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, contact: *
|
||||||
|
* *
|
||||||
|
* Free Software Foundation Voice: +1-617-542-5942 *
|
||||||
|
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
||||||
|
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
||||||
|
********************************************************************/
|
||||||
|
|
||||||
|
#include <config.h>
|
||||||
|
#include <glib/gi18n.h>
|
||||||
|
#include <glib/gstdio.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
#include "gnc-ws-server.h"
|
||||||
|
#include "gnc-ws-protocol.h"
|
||||||
|
|
||||||
|
#define GNC_WS_SERVER_PATH "gnc-ws-server-path"
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (GncWsServer, gnc_ws_server, G_TYPE_OBJECT)
|
||||||
|
|
||||||
|
#define PORT 8080
|
||||||
|
#define BLOCK_SIZE 1024
|
||||||
|
|
||||||
|
static GncWsServer *gws = NULL;
|
||||||
|
|
||||||
|
/* Signal codes */
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
OPEN,
|
||||||
|
MESSAGE,
|
||||||
|
CLOSE,
|
||||||
|
LAST_SIGNAL
|
||||||
|
};
|
||||||
|
|
||||||
|
static guint ws_server_signals [LAST_SIGNAL] = { 0 };
|
||||||
|
|
||||||
|
static void
|
||||||
|
gnc_ws_server_init (GncWsServer *self)
|
||||||
|
{
|
||||||
|
g_print("%s called\n",__FUNCTION__);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gnc_ws_server_dispose (GObject *object)
|
||||||
|
{
|
||||||
|
g_print("%s called\n",__FUNCTION__);
|
||||||
|
g_return_if_fail (object != NULL);
|
||||||
|
|
||||||
|
GncWsServer *gws = (GncWsServer*)object;
|
||||||
|
|
||||||
|
g_print("ws service active %d\n", g_socket_service_is_active (gws->service));
|
||||||
|
|
||||||
|
if (gws->incoming_id != 0)
|
||||||
|
g_signal_handler_disconnect (G_OBJECT(gws->service), gws->incoming_id);
|
||||||
|
gws->incoming_id = 0;
|
||||||
|
|
||||||
|
g_socket_service_stop (gws->service);
|
||||||
|
|
||||||
|
g_print(" ws service active %d\n", g_socket_service_is_active (gws->service));
|
||||||
|
|
||||||
|
G_OBJECT_CLASS(gnc_ws_server_parent_class)->dispose (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gnc_ws_server_finalize (GObject *object)
|
||||||
|
{
|
||||||
|
g_print("%s called\n",__FUNCTION__);
|
||||||
|
|
||||||
|
gws = NULL;
|
||||||
|
|
||||||
|
G_OBJECT_CLASS(gnc_ws_server_parent_class)->finalize (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gnc_ws_server_class_init (GncWsServerClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
|
||||||
|
g_print("%s called\n",__FUNCTION__);
|
||||||
|
gobject_class->dispose = gnc_ws_server_dispose;
|
||||||
|
gobject_class->finalize = gnc_ws_server_finalize;
|
||||||
|
|
||||||
|
ws_server_signals [OPEN] =
|
||||||
|
g_signal_new ("open",
|
||||||
|
G_OBJECT_CLASS_TYPE(gobject_class),
|
||||||
|
G_SIGNAL_RUN_FIRST,
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
G_TYPE_NONE,
|
||||||
|
1,
|
||||||
|
G_TYPE_STRING);
|
||||||
|
|
||||||
|
ws_server_signals [MESSAGE] =
|
||||||
|
g_signal_new ("message",
|
||||||
|
G_OBJECT_CLASS_TYPE(gobject_class),
|
||||||
|
G_SIGNAL_RUN_FIRST,
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
G_TYPE_NONE,
|
||||||
|
2,
|
||||||
|
G_TYPE_STRING,
|
||||||
|
G_TYPE_STRING);
|
||||||
|
|
||||||
|
ws_server_signals [CLOSE] =
|
||||||
|
g_signal_new ("close",
|
||||||
|
G_OBJECT_CLASS_TYPE(gobject_class),
|
||||||
|
G_SIGNAL_RUN_FIRST,
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
G_TYPE_NONE,
|
||||||
|
1,
|
||||||
|
G_TYPE_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
send_message_bytes (GSocketConnection *connection, GBytes *bytes)
|
||||||
|
{
|
||||||
|
gboolean ret = TRUE;
|
||||||
|
GError *error = NULL;
|
||||||
|
GOutputStream *ostream = g_io_stream_get_output_stream (G_IO_STREAM(connection));
|
||||||
|
|
||||||
|
gssize data_out = g_output_stream_write_bytes (ostream, bytes, NULL, &error);
|
||||||
|
#ifdef G_OS_WIN32
|
||||||
|
g_print("%s called, connection %p, data out sent %d\n",__FUNCTION__, connection, data_out);
|
||||||
|
#else
|
||||||
|
g_print("%s called, connection %p, data out sent %ld\n",__FUNCTION__, connection, data_out);
|
||||||
|
#endif
|
||||||
|
if (error != NULL)
|
||||||
|
{
|
||||||
|
g_error ("%s", error->message);
|
||||||
|
g_print("send error '%s'\n", error->message);
|
||||||
|
g_clear_error (&error);
|
||||||
|
ret = FALSE;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct ConnHashData
|
||||||
|
{
|
||||||
|
GSocketConnection *connection;
|
||||||
|
const gchar *id;
|
||||||
|
};
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
connection_hash_table_find (gpointer key, gpointer value, gpointer user_data)
|
||||||
|
{
|
||||||
|
struct ConnHashData *data = user_data;
|
||||||
|
|
||||||
|
if (g_str_has_suffix (data->id, value))
|
||||||
|
{
|
||||||
|
data->connection = key;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
gnc_ws_server_send_message (GncWsServer *gws, const gchar *id, const gchar *message)
|
||||||
|
{
|
||||||
|
struct ConnHashData *data = g_new (struct ConnHashData, 1);
|
||||||
|
data->id = id;
|
||||||
|
data->connection = NULL;
|
||||||
|
|
||||||
|
g_print("%s called, id '%s', message '%s'\n",__FUNCTION__, id, message);
|
||||||
|
|
||||||
|
g_hash_table_foreach (gws->connections_hash,
|
||||||
|
(GHFunc)connection_hash_table_find,
|
||||||
|
data);
|
||||||
|
|
||||||
|
if (data->connection)
|
||||||
|
{
|
||||||
|
GBytes *bytes = gnc_ws_send_message (message);
|
||||||
|
send_message_bytes (data->connection, bytes);
|
||||||
|
g_bytes_unref (bytes);
|
||||||
|
}
|
||||||
|
g_free (data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
send_handshake_message (GSocketConnection *connection, const gchar *message)
|
||||||
|
{
|
||||||
|
gboolean ret = TRUE;
|
||||||
|
GError *error = NULL;
|
||||||
|
GOutputStream *ostream = g_io_stream_get_output_stream (G_IO_STREAM(connection));
|
||||||
|
|
||||||
|
#ifdef G_OS_WIN32
|
||||||
|
g_print("%s called, connection %p, msg len %d\n",__FUNCTION__, connection, strlen (message));
|
||||||
|
#else
|
||||||
|
g_print("%s called, connection %p, msg len %ld\n",__FUNCTION__, connection, strlen (message));
|
||||||
|
#endif
|
||||||
|
g_output_stream_write (ostream,
|
||||||
|
message,
|
||||||
|
strlen (message),
|
||||||
|
NULL,
|
||||||
|
&error);
|
||||||
|
|
||||||
|
if (error != NULL)
|
||||||
|
{
|
||||||
|
g_error ("%s", error->message);
|
||||||
|
g_print("send error '%s'\n", error->message);
|
||||||
|
g_clear_error (&error);
|
||||||
|
ret = FALSE;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct ConnData
|
||||||
|
{
|
||||||
|
GncWsServer *gws;
|
||||||
|
GSocketConnection *connection;
|
||||||
|
char message[BLOCK_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
bytes_ready_cb (GObject *source_object,
|
||||||
|
GAsyncResult *res,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
GInputStream *istream = G_INPUT_STREAM(source_object);
|
||||||
|
GError *error = NULL;
|
||||||
|
struct ConnData *data = user_data;
|
||||||
|
GncWsServer *gws = data->gws;
|
||||||
|
|
||||||
|
GBytes *bytes_in = g_input_stream_read_bytes_finish (istream, res, &error);
|
||||||
|
|
||||||
|
g_print("%s called, istream %p\n",__FUNCTION__, istream);
|
||||||
|
|
||||||
|
if (error != NULL)
|
||||||
|
{
|
||||||
|
g_error ("%s", error->message);
|
||||||
|
g_print("bytes service error is: %s\n", error->message);
|
||||||
|
g_clear_error (&error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_bytes_get_size (bytes_in) != 0)
|
||||||
|
{
|
||||||
|
gint opcode;
|
||||||
|
gchar *message = gnc_ws_parse_bytes_in (bytes_in, &opcode);
|
||||||
|
|
||||||
|
if ((opcode == OPCODE_TEXT) && message)
|
||||||
|
{
|
||||||
|
gchar *id = g_hash_table_lookup (gws->connections_hash, data->connection);
|
||||||
|
|
||||||
|
if (id)
|
||||||
|
g_signal_emit_by_name (data->gws, "message", id, message);
|
||||||
|
else
|
||||||
|
g_print("Error: Lookup Error\n");
|
||||||
|
}
|
||||||
|
g_free (message);
|
||||||
|
g_bytes_unref (bytes_in);
|
||||||
|
|
||||||
|
if (opcode == OPCODE_CLOSE)
|
||||||
|
{
|
||||||
|
gchar *id = g_hash_table_lookup (gws->connections_hash, data->connection);
|
||||||
|
|
||||||
|
if (id)
|
||||||
|
g_signal_emit_by_name (data->gws, "close", id);
|
||||||
|
else
|
||||||
|
g_print("Error: Lookup Error\n");
|
||||||
|
|
||||||
|
g_hash_table_remove (gws->connections_hash, data->connection);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g_input_stream_read_bytes_async (istream, 8192, G_PRIORITY_DEFAULT,
|
||||||
|
NULL, bytes_ready_cb, user_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
message_ready_cb (GObject *source_object,
|
||||||
|
GAsyncResult *res,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
GInputStream *istream = G_INPUT_STREAM(source_object);
|
||||||
|
GError *error = NULL;
|
||||||
|
struct ConnData *data = user_data;
|
||||||
|
int count;
|
||||||
|
|
||||||
|
count = g_input_stream_read_finish (istream, res, &error);
|
||||||
|
|
||||||
|
g_print("%s called, istream %p, count %d\n",__FUNCTION__, istream, count);
|
||||||
|
|
||||||
|
if (count == -1)
|
||||||
|
{
|
||||||
|
g_print ("Error: receiving message\n");
|
||||||
|
if (error != NULL)
|
||||||
|
{
|
||||||
|
g_print("Error: incoming stream error: %s\n", error->message);
|
||||||
|
g_clear_error (&error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gchar *id = NULL;
|
||||||
|
gchar *handshake_message = gnc_ws_make_handshake (data->message, &id);
|
||||||
|
|
||||||
|
if (handshake_message)
|
||||||
|
{
|
||||||
|
if (send_handshake_message (data->connection, handshake_message))
|
||||||
|
g_input_stream_read_bytes_async (istream, 8192, G_PRIORITY_DEFAULT,
|
||||||
|
NULL, bytes_ready_cb, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id)
|
||||||
|
{
|
||||||
|
g_hash_table_insert (gws->connections_hash, data->connection, g_strdup(id));
|
||||||
|
g_signal_emit_by_name (data->gws, "open", id);
|
||||||
|
}
|
||||||
|
g_free (handshake_message);
|
||||||
|
g_free (id);
|
||||||
|
|
||||||
|
// g_object_unref (G_SOCKET_CONNECTION(data->connection));
|
||||||
|
// g_free (data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
incoming_callback (GSocketService *service,
|
||||||
|
GSocketConnection *connection,
|
||||||
|
GObject *source_object,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
GncWsServer *gws = user_data;
|
||||||
|
GInputStream *istream = g_io_stream_get_input_stream (G_IO_STREAM(connection));
|
||||||
|
struct ConnData *data = g_new (struct ConnData, 1);
|
||||||
|
|
||||||
|
g_print("%s called, connection %p\n",__FUNCTION__, connection);
|
||||||
|
|
||||||
|
data->connection = g_object_ref (connection);
|
||||||
|
data->gws = gws;
|
||||||
|
|
||||||
|
g_input_stream_read_async (istream, data->message, sizeof (data->message),
|
||||||
|
G_PRIORITY_DEFAULT, NULL, message_ready_cb, data);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
GncWsServer *
|
||||||
|
gnc_ws_server_new (void)
|
||||||
|
{
|
||||||
|
g_print("%s called\n",__FUNCTION__);
|
||||||
|
|
||||||
|
if (gws)
|
||||||
|
return gws;
|
||||||
|
|
||||||
|
gws = g_object_new (GNC_TYPE_WS_SERVER, NULL);
|
||||||
|
|
||||||
|
g_print(" ws gws %p\n", gws);
|
||||||
|
|
||||||
|
gws->service = g_socket_service_new ();
|
||||||
|
GError *error = NULL;
|
||||||
|
gboolean ret = g_socket_listener_add_inet_port (G_SOCKET_LISTENER(gws->service),
|
||||||
|
PORT, NULL, &error);
|
||||||
|
|
||||||
|
if (ret && error != NULL)
|
||||||
|
{
|
||||||
|
g_error ("%s", error->message);
|
||||||
|
g_print("service error is: %s\n", error->message);
|
||||||
|
g_clear_error (&error);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
gws->connections_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal,
|
||||||
|
NULL, g_free);
|
||||||
|
|
||||||
|
g_socket_service_start (gws->service);
|
||||||
|
|
||||||
|
gws->incoming_id = g_signal_connect (gws->service, "incoming",
|
||||||
|
G_CALLBACK(incoming_callback), gws);
|
||||||
|
|
||||||
|
g_print(" ws service active %d\n", g_socket_service_is_active (gws->service));
|
||||||
|
|
||||||
|
return gws;
|
||||||
|
}
|
57
gnucash/html/gnc-ws-server.h
Normal file
57
gnucash/html/gnc-ws-server.h
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/********************************************************************
|
||||||
|
* gnc-ws-server.h -- basic websocket server *
|
||||||
|
* Copyright (C) 2024 Bob Fewell *
|
||||||
|
* *
|
||||||
|
* 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, contact: *
|
||||||
|
* *
|
||||||
|
* Free Software Foundation Voice: +1-617-542-5942 *
|
||||||
|
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
||||||
|
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
||||||
|
\********************************************************************/
|
||||||
|
|
||||||
|
#ifndef WS_SERVER_H
|
||||||
|
#define WS_SERVER_H
|
||||||
|
|
||||||
|
#include <config.h>
|
||||||
|
|
||||||
|
#define GNC_TYPE_WS_SERVER (gnc_ws_server_get_type ())
|
||||||
|
#define GNC_WS_SERVER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNC_TYPE_WS_SERVER, GncWsServer))
|
||||||
|
#define GNC_WS_SERVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNC_TYPE_WS_SERVER, GncWsServerClass))
|
||||||
|
#define GNC_IS_WS_SERVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNC_TYPE_WS_SERVER))
|
||||||
|
#define GNC_IS_WS_SERVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GNC_TYPE_WS_SERVER))
|
||||||
|
#define GNC_WS_SERVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNC_TYPE_WS_SERVER, GncWsServerClass))
|
||||||
|
|
||||||
|
typedef struct _GncWsServer GncWsServer;
|
||||||
|
typedef struct _GncWsServerClass GncWsServerClass;
|
||||||
|
|
||||||
|
struct _GncWsServer
|
||||||
|
{
|
||||||
|
GObject parent;
|
||||||
|
GSocketService *service;
|
||||||
|
gulong incoming_id;
|
||||||
|
GHashTable *connections_hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _GncWsServerClass
|
||||||
|
{
|
||||||
|
GObjectClass parent;
|
||||||
|
};
|
||||||
|
|
||||||
|
GType gnc_ws_server_get_type (void) G_GNUC_CONST;
|
||||||
|
|
||||||
|
GncWsServer *gnc_ws_server_new (void);
|
||||||
|
|
||||||
|
void gnc_ws_server_send_message (GncWsServer *gws, const gchar *id, const gchar *message);
|
||||||
|
|
||||||
|
#endif
|
@ -185,6 +185,66 @@
|
|||||||
(push (list "</style>" style-text "<style type=\"text/css\">\n")))
|
(push (list "</style>" style-text "<style type=\"text/css\">\n")))
|
||||||
(if (not (string-null? title))
|
(if (not (string-null? title))
|
||||||
(push (list "</title>" title "<title>\n")))
|
(push (list "</title>" title "<title>\n")))
|
||||||
|
|
||||||
|
(push "<pre id='log'></pre>\n")
|
||||||
|
|
||||||
|
(push "<script>\n")
|
||||||
|
|
||||||
|
(push " // helper function: log message to screen\n")
|
||||||
|
(push " function log(msg) {\n")
|
||||||
|
(push " document.getElementById('log').textContent += msg + '\\n';\n")
|
||||||
|
(push " }\n")
|
||||||
|
|
||||||
|
(push " var url=location.href;\n")
|
||||||
|
(push " log(url);\n")
|
||||||
|
(push " var urlFilename = url.substring(url.lastIndexOf('/')+1);\n")
|
||||||
|
(push " log(urlFilename);\n")
|
||||||
|
|
||||||
|
(push " // setup websocket with callbacks\n")
|
||||||
|
(push " var ws = new WebSocket('ws://localhost:8080/' + urlFilename);\n")
|
||||||
|
(push " ws.onopen = function() {\n")
|
||||||
|
(push " log('CONNECT');\n")
|
||||||
|
(push " };\n")
|
||||||
|
(push " ws.onclose = function() {\n")
|
||||||
|
(push " log('DISCONNECT');\n")
|
||||||
|
(push " };\n")
|
||||||
|
(push " ws.onmessage = function(event) {\n")
|
||||||
|
(push " log('MESSAGE: ' + event.data);\n")
|
||||||
|
|
||||||
|
(push " if (event.data.localeCompare('RELOAD') == 0) {\n")
|
||||||
|
(push " location.reload();\n")
|
||||||
|
(push " }\n");
|
||||||
|
|
||||||
|
(push " if (event.data.localeCompare('PRINT') == 0) {\n")
|
||||||
|
(push " window.print();\n")
|
||||||
|
(push " }\n");
|
||||||
|
|
||||||
|
|
||||||
|
(push " };\n")
|
||||||
|
(push " ws.addEventListener('message', (event) => {\n")
|
||||||
|
(push " console.log('Message from server ', event.data);\n")
|
||||||
|
(push " });\n")
|
||||||
|
|
||||||
|
(push "window.onclick = function(e) {\n")
|
||||||
|
(push " var node = e.target;\n")
|
||||||
|
(push " while (node != undefined && node.localName != 'a') {\n")
|
||||||
|
(push " node = node.parentNode;\n")
|
||||||
|
(push " }\n")
|
||||||
|
(push " if (node != undefined)\n")
|
||||||
|
(push " {\n")
|
||||||
|
(push " log(node.href);\n")
|
||||||
|
|
||||||
|
(push " ws.send(node.href);\n")
|
||||||
|
|
||||||
|
(push " return false; // stop handling the click\n")
|
||||||
|
(push " } else {\n")
|
||||||
|
(push " return true; // handle other clicks\n")
|
||||||
|
(push " }\n")
|
||||||
|
(push "}\n")
|
||||||
|
|
||||||
|
(push "</script>\n")
|
||||||
|
|
||||||
|
|
||||||
(push "</head>")
|
(push "</head>")
|
||||||
|
|
||||||
;; this lovely little number just makes sure that <body>
|
;; this lovely little number just makes sure that <body>
|
||||||
|
Loading…
Reference in New Issue
Block a user