Previous: , Up: Hello Emacsy   [Contents][Index]


4.4 Plaintext Please

4.4.1 hello-emacsy.c

/*
   Emacsy --- An embeddable Emacs-like library using GNU Guile

   Copyright (C) 2012, 2013 Shane Celis <shane.celis@gmail.com>
   Copyright (C) 2019, Jan (janneke) Nieuwenhuizen <janneke@gnu.org>

   This file is part of Emacsy.

   Emacsy 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 3 of the License, or
   (at your option) any later version.

   Emacsy 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 Emacsy.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * Let's exercise these functions in a minimal FreeGLUT program we'll call
 * @verb{|hello-emacsy|}.@footnote{Note: Emacsy does not rely on FreeGLUT.
 * One could use Gtk+, Ncurses, Qt, or whatever}.  This simple program
 * will display an integer, the variable @var{counter}, that one can
 * increment or decrement.
 *
 * @image{images/minimal-emacsy-example,,,,.png}
 */

#ifndef SCM_MAGIC_SNARFER
#include <libgen.h>
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <emacsy.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#endif
#include <libguile.h>

void display_func ();
void keyboard_func (unsigned char glut_key, int x, int y);
void draw_string (int, int, char*);
char * try_load_startup (char const* prefix, char const* dir, char const* startup_script);
void primitives_init ();

/*
 * @defvar int counter = 0;
 * Hello Emacsy's state is captured by one global variable.
 * Hello Emacsy will display this number.
 * @end defvar
 */
int counter = 0;
int interactive = 1;

/*
 * Initialize everything in @var{main} and enter our runloop.
 */
int
main (int argc, char *argv[])
{
  int err;
/* glutInit (&argc, argv);
 * Initialize GLUT.
 */
  glutInit (&argc, argv);
  glutInitDisplayMode (GLUT_RGB|GLUT_DOUBLE);
  glutInitWindowSize (500, 500);
  glutCreateWindow ("Hello, Emacsy!");
  glutDisplayFunc (display_func);
  if (interactive)
    glutKeyboardFunc (keyboard_func);
/* void scm_init_guile ();
 * Initialize Guile.
 */
  scm_init_guile ();
/* emacsy_initialize (@dots {});
 * Initialize Emacsy.
 */
  if (argc == 2 && strcmp ("--batch", argv[1]) == 0)
    interactive = 0;
  err = emacsy_initialize (interactive
                           ? EMACSY_INTERACTIVE
                           : EMACSY_NON_INTERACTIVE);
  if (err)
    exit (err);
/* primitives_init ();
 * Register primitives.
 */
  primitives_init ();

/* char * try_load_startup (@dots{});
 * Try to load @file{hello-emacsy.scm}
 */
  char const *startup_script = "hello-emacsy.scm";

  char prefix[PATH_MAX];
  strcpy (prefix, argv[0]);
  if (getenv ("_"))
    strcpy (prefix, getenv ("_"));
  dirname (dirname (prefix));

  if (!try_load_startup (0, 0, startup_script)
      &&!try_load_startup (getenv ("EMACSY_SYSCONFDIR"), "/", startup_script)
      &&!try_load_startup (prefix, "/", startup_script)
      &&!try_load_startup (prefix, "/etc/emacsy/", startup_script))
    fprintf (stderr, "error: failed to find '%s'.\n", startup_script);
/* void glutMainLoop ();
 * Enter GLUT main loop, not return.
 */
  glutMainLoop ();
  return 0;
}

/*
 * @subsection Runloop Interaction
 *
 * Let's look at how Emacsy interacts with your application's runloop
 * since that's probably the most concerning part of embedding.  First,
 * let's pass some input to Emacsy.
 */

/* void keyboard_func (unsigned char glut_key, int x, int y)
 * Send key events to Emacsy.
 */
void
keyboard_func (unsigned char glut_key, int x, int y)
{
/*
 * int key; // The Key event (not processed yet).
 */
  int key;
  int mod_flags;
  int glut_mod_flags = glutGetModifiers ();
  mod_flags = 0;
  if (glut_mod_flags & GLUT_ACTIVE_SHIFT)
    mod_flags |= EMACSY_MODKEY_SHIFT;
  if (glut_mod_flags & GLUT_ACTIVE_CTRL)
    mod_flags |= EMACSY_MODKEY_CONTROL;
  if (glut_mod_flags & GLUT_ACTIVE_ALT)
    mod_flags |= EMACSY_MODKEY_META;
  if (glut_key == 8)
    glut_key = 127;
  else if (glut_key == 127)
    {
      glut_key = 4;
      mod_flags += EMACSY_MODKEY_CONTROL;
    }
/*
 * The keys @verb{|C-a|} and @verb{|C-b|} return @code{1} and @code{2}
 * respectively.  We want to map these to their actual character values.
 */
  key = mod_flags & EMACSY_MODKEY_CONTROL
    ? glut_key + ('a' - 1)
    : glut_key;
  emacsy_key_event (key, mod_flags);
  glutPostRedisplay ();
}

/* void display_func ()
 * The function @var{display_func} is run for every frame that's
 * drawn. It's effectively our runloop, even though the actual runloop is
 * in FreeGLUT.
 *
 * Our application has just one job: Display the counter variable.
 */
void
display_func ()
{
/* glClear (GL_COLOR_BUFFER_BIT);
 * Setup the display buffer the drawing.
 */
  glClear (GL_COLOR_BUFFER_BIT);

  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  glOrtho (0.0, 500.0, 0.0, 500.0, -2.0, 500.0);
  gluLookAt (0,   0,   2,
             0.0, 0.0, 0.0,
             0.0, 1.0, 0.0);

  glMatrixMode (GL_MODELVIEW);
  glColor3f (1, 1, 1);

  char counter_string[255];
  sprintf (counter_string, "%d", counter);
  draw_string (250, 250, counter_string);

/*
 * Process events in Emacsy.
 */
  if (emacsy_tick () & EMACSY_QUIT_APPLICATION_P)
    {
      emacsy_terminate ();
      exit (0);
    }
  glutSetWindowTitle (emacsy_current_buffer ());

/*
 * Display Emacsy message/echo area.
 */
  draw_string (0, 5, emacsy_message_or_echo_area ());

/*
 *  Display Emacsy mode line.
 */
  draw_string (0, 30, emacsy_mode_line ());

  glutSwapBuffers ();
}

/* void draw_string (int x, int y, char *string)
 *
 * Draw a string function.
 * Draws a string at (x, y) on the screen.
 */
void
draw_string (int x, int y, char *string)
{
  glLoadIdentity ();
  glTranslatef (x, y, 0.);
  glScalef (0.2, 0.2, 1.0);
  while (*string)
    glutStrokeCharacter (GLUT_STROKE_ROMAN,
                         *string++);
}

/*
 * At this point, our application can process key events, accept input on
 * the minibuffer, and use nearly all of the facilities that Emacsy
 * offers, but it can't change any application state, which makes it not
 * very interesting yet.
 */

/*
 * @subsection Plugging Into Your App
 */

//

/*
 * @deffn {Scheme Procedure} get-counter
 * @deffnx {C Function} SCM scm_get_counter ()
 * Let's define a new primitive Scheme procedure @var{get-counter}, so
 * Emacsy can access the application's state.  This will define
 * a @var{C} function @code{SCM scm_get_counter (void)} and a Scheme procedure
 * @code{(get-counter)}.
 *
 * @end deffn
 */

SCM_DEFINE (scm_get_counter, "get-counter",
            /* required arg count    */ 0,
            /* optional arg count    */ 0,
            /* variable length args? */ 0,
            (),
            "Returns value of counter.")
{
  return scm_from_int (counter);
}

/*
 * @deffn {Scheme Procedure} set-counter! value
 * @deffnx {C Function} SCM scm_set_counter_x (SCM value)
 * Let's define another primitive Scheme procedure to alter the
 * application's state.
 * @end deffn
 */

SCM_DEFINE (scm_set_counter_x, "set-counter!",
            /* required, optional, var. length? */
            1, 0, 0,
            (SCM value),
            "Sets value of counter.")
{
  counter = scm_to_int (value);
  glutPostRedisplay ();
  return SCM_UNSPECIFIED;
}

/* void primitives_init ()
 * Once we have written these primitive procedures, we need to register
 * them with the Scheme runtime.
 */
void
primitives_init ()
{
#ifndef SCM_MAGIC_SNARFER
#include "hello-emacsy.c.x"
#endif
}

/* char * try_load_startup (char const* prefix, char const* dir, char const* startup_script)
 * Locate the @file{hello-emacsy.scm} Guile initialization and load it.
 */
char *
try_load_startup (char const* prefix, char const* dir, char const* startup_script)
{
  static char file_name[PATH_MAX];
  if (prefix)
    strcpy (file_name, prefix);
  if (dir)
    strcat (file_name, dir);
  strcat (file_name, startup_script);

  if (access (file_name, R_OK) != -1)
    {
      fprintf (stderr, "Loading '%s'.\n", file_name);
      scm_c_primitive_load (file_name);
      return file_name;
    }
  else
    fprintf (stderr, "no such file '%s'.\n", file_name);

  return 0;
}

4.4.2 hello-emacsy.scm

;;; Emacsy --- An embeddable Emacs-like library using GNU Guile
;;;
;;; Copyright (C) 2012, 2013 Shane Celis <shane.celis@gmail.com>
;;;
;;; This file is part of Emacsy.
;;;
;;; Emacsy 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 3 of the License, or
;;; (at your option) any later version.
;;;
;;; Emacsy 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 Emacsy.  If not, see <http://www.gnu.org/licenses/>.

;; We generate the file @file{example/hello-emacsy.c.x} by running the
;; command: @code{guile-snarf example/hello-emacsy.c}.  Emacsy can now
;; access and alter the application's internal state.
;;.

;; @subsection Changing the UI Now let's use these new procedures to
;; create interactive commands and bind them to keys by changing our
;; config file @file{example/hello-emacsy.scm}.
(use-modules (emacsy emacsy))

;;.
(define-interactive (incr-counter #:optional (n (universal-argument-pop!)))
 "Increment the counter."
 (set-counter! (+ (get-counter) n)))

;;.
(define-interactive (decr-counter #:optional (n (universal-argument-pop!)))
 "Decrement the counter."
 (set-counter! (- (get-counter) n)))

;; Bind @var{inc-counter} to @code{=}.
(define-key global-map "=" 'incr-counter)
;; Bind @var{inc-counter} to @code{-}.
(define-key global-map "-" 'decr-counter)

;; We can now hit @verb{|-|} and @verb{|=|} to decrement and increment the
;; @var{counter}. This is fine, but what else can we do with it?  We could
;; make a macro that increments 5 times by hitting
;; @verb{|C-x ( = = = = = C-x )|}, then hit @verb{|C-e|} to run that macro.
;; (set! debug-on-error? #t)

;; Let's implement another command that will ask the user for a number to
;; set the counter to.
;;.

;; Now we can hit @verb{|M-x change-counter|} and we'll be prompted for
;; the new value we want.  There we have it.  We have made the simplest
;; application ever more @emph{Emacs-y}.
(define-interactive (change-counter)
 "Change the counter to a new value."
 (set-counter!
   (string->number
     (read-from-minibuffer
       "New counter value: "))))

;; @subsection Changing it at Runtime
;;
;; We can add commands easily by changing and reloading the file.  But
;; we can do better.  Let's start a REPL we can connect to.
;; @file{example/hello-emacsy.scm}.
;;.

;; @example
;; (use-modules (system repl server))
;; (spawn-server)
;; @end example
;; Start a server on port 37146.
;;.

;; Start a server on port 37146.
(use-modules (system repl server))
(spawn-server)

4.4.3 emacsy.h

/*
  Emacsy --- An embeddable Emacs-like library using GNU Guile

  Copyright (C) 2012, 2013 Shane Celis <shane.celis@gmail.com>
  Copyright (C) 2019, Jan (janneke) Nieuwenhuizen <janneke@gnu.org>

  This file is part of Emacsy.

  Emacsy 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 3 of the License, or
  (at your option) any later version.

  Emacsy 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 Emacsy.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef __EMACSY_H
#define __EMACSY_H 1

#ifdef __cplusplus
 extern "C" {
#endif

#include <libguile.h>

/* Here are the constants for the C API.                                    */
/*                                                                          */
/*                                                                          */
/* <emacsy-c-api:Defines>=                                                  */
#define EMACSY_MODKEY_COUNT   6

#define EMACSY_MODKEY_ALT     1 // A
#define EMACSY_MODKEY_CONTROL 2 // C
#define EMACSY_MODKEY_HYPER   4 // H
#define EMACSY_MODKEY_META    8 // M
#define EMACSY_MODKEY_SUPER  16 // s
#define EMACSY_MODKEY_SHIFT  32 // S

#define EMACSY_MOUSE_BUTTON_DOWN  0
#define EMACSY_MOUSE_BUTTON_UP    1
#define EMACSY_MOUSE_MOTION       2

#define EMACSY_INTERACTIVE        1
#define EMACSY_NON_INTERACTIVE    0

/* Here are the return flags that may be returned by \verb|emacsy_tick|.    */
/*                                                                          */
/*                                                                          */
/* <emacsy-c-api:Defines>=                                                  */
#define EMACSY_QUIT_APPLICATION_P       1
#define EMACSY_ECHO_AREA_UPDATED_P      2
#define EMACSY_MODELINE_UPDATED_P       4
#define EMACSY_RAN_UNDEFINED_COMMAND_P  8

/*
 * Emacsy provides a C API to ease integration with C and C++
 * programs. The C API is given below.
 */

/* Initialize Emacsy. */
int  emacsy_initialize (int init_flags);

/* Enqueue a keyboard event. */
void emacsy_key_event (int char_code,
                       int modifier_key_flags);

/* Enqueue a mouse event. */
void emacsy_mouse_event (int x, int y,
                         int state,
                         int button,
                         int modifier_key_flags);

/* Run an iteration of Emacsy's event loop, does not block. */
int emacsy_tick ();

/* Return the message or echo area. */
char *emacsy_message_or_echo_area ();

/* Return the mode line. */
char *emacsy_mode_line ();

/* Return the name of the current buffer. */
char *emacsy_current_buffer ();

/* Run a hook. */
int  emacsy_run_hook_0 (char const *hook_name);

/* Return the minibuffer point. */
int  emacsy_minibuffer_point ();

/* Terminate Emacsy; run termination hook. */
int  emacsy_terminate ();

/* Attempt to load a module. */
int emacsy_load_module (char const *module_name);

/* Load a file in the emacsy environment. */
//int emacsy_load(const char *file_name);

/* Convert the modifier_key_flags into a Scheme list of symbols. */
SCM modifier_key_flags_to_list(int modifier_key_flags);

/* SCM scm_c_string_to_symbol (char const* str) */
SCM scm_c_string_to_symbol (char const* str);

/* Ref @var{name} from emacsy module. */
SCM scm_c_emacsy_ref (char const* name);

#ifdef __cplusplus
 }
#endif

#endif // __EMACSY_H

Previous: , Up: Hello Emacsy   [Contents][Index]