diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | .gitmodules | 3 | ||||
-rw-r--r-- | CMakeLists.txt | 86 | ||||
-rw-r--r-- | LICENSE | 14 | ||||
-rw-r--r-- | README | 29 | ||||
-rw-r--r-- | autistdraw.c | 232 | ||||
-rw-r--r-- | config.h.in | 11 |
7 files changed, 379 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1108dc5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Backup files +*.*~ +# IDE project files +/CMakeLists.txt.user diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ad38bca --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "termo"] + path = termo + url = git://github.com/pjanouch/termo.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..08ca44f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,86 @@ +project (autistdraw C) +cmake_minimum_required (VERSION 2.8.5) + +# Moar warnings +if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC) + set (CMAKE_C_FLAGS "-std=gnu99") + set (CMAKE_C_FLAGS_DEBUG + "${CMAKE_C_FLAGS_DEBUG} -Wall -Wextra -Wno-missing-field-initializers") +endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC) + +# Build options +option (USE_SYSTEM_TERMO "Don't compile our own termo, use the system one" OFF) + +# Version +set (project_VERSION_MAJOR "0") +set (project_VERSION_MINOR "1") +set (project_VERSION_PATCH "0") + +set (project_VERSION "${project_VERSION_MAJOR}") +set (project_VERSION "${project_VERSION}.${project_VERSION_MINOR}") +set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}") + +# Dependencies +find_package (PkgConfig REQUIRED) +pkg_check_modules (dependencies REQUIRED ncursesw libuv) + +if (USE_SYSTEM_TERMO) + find_package (Termo REQUIRED) +else (USE_SYSTEM_TERMO) + add_subdirectory (termo EXCLUDE_FROM_ALL) + # We don't have many good choices when we don't want to install it and want + # to support older versions of CMake; this is a relatively clean approach + # (other possibilities: setting a variable in the parent scope, using a + # cache variable, writing a special config file with build paths in it and + # including it here, or setting a custom property on the targets). + get_directory_property (Termo_INCLUDE_DIRS + DIRECTORY termo INCLUDE_DIRECTORIES) + set (Termo_LIBRARIES termo-static) +endif (USE_SYSTEM_TERMO) + +include_directories (${dependencies_INCLUDE_DIRS} ${Termo_INCLUDE_DIRS}) + +# Configuration +include (CheckFunctionExists) +set (CMAKE_REQUIRED_LIBRARIES ${dependencies_LIBRARIES}) +CHECK_FUNCTION_EXISTS ("resize_term" HAVE_RESIZE_TERM) + +# Project source files +set (project_sources ${PROJECT_NAME}.c) +set (project_headers ${CMAKE_CURRENT_BINARY_DIR}/config.h) + +# Project libraries +set (project_libraries ${dependencies_LIBRARIES} termo-static) + +# Generate a configuration file +configure_file (${CMAKE_CURRENT_SOURCE_DIR}/config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/config.h) +include_directories (${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) + +# Build the main executable and link it +add_executable (${PROJECT_NAME} ${project_sources} ${project_headers}) +target_link_libraries (${PROJECT_NAME} ${project_libraries}) + +# The files to be installed +include (GNUInstallDirs) +install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR}) +install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR}) + +# CPack +set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Terminal drawing application") +set (CPACK_PACKAGE_VENDOR "Premysl Janouch") +set (CPACK_PACKAGE_CONTACT "Přemysl Janouch <p.janouch@gmail.com>") +set (CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") +set (CPACK_PACKAGE_VERSION_MAJOR ${project_VERSION_MAJOR}) +set (CPACK_PACKAGE_VERSION_MINOR ${project_VERSION_MINOR}) +set (CPACK_PACKAGE_VERSION_PATCH ${project_VERSION_PATCH}) +set (CPACK_GENERATOR "TGZ;ZIP") +set (CPACK_PACKAGE_FILE_NAME + "${PROJECT_NAME}-${project_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") +set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}-${project_VERSION}") +set (CPACK_SOURCE_GENERATOR "TGZ;ZIP") +set (CPACK_SOURCE_IGNORE_FILES "/\\\\.git;/build;/CMakeLists.txt.user") +set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${project_VERSION}") + +include (CPack) + @@ -0,0 +1,14 @@ + Copyright (c) 2014, Přemysl Janouch <p.janouch@gmail.com> + All rights reserved. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. @@ -0,0 +1,29 @@ +autistdraw +========== + +`autistdraw' will be a terminal drawing application with multiplayer support. + +Building and Running +-------------------- +Build dependencies: GCC/Clang, pkg-config, GNU make, Jansson, cURL, readline + +If you don't have Clang, you can edit the Makefile to use GCC or TCC, they work +just as good. But there's no CMake support yet, so I force it in the Makefile. + + $ git clone https://github.com/pjanouch/autistdraw.git + $ git submodule init + $ git submodule update + $ mkdir build + $ cmake .. -DCMAKE_BUILD_TYPE=Debug + $ make + $ ./autistdraw + +License +------- +`autistdraw' is written by Přemysl Janouch <p.janouch@gmail.com>. + +You may use the software under the terms of the ISC license, the text of which +is included within the package, or, at your option, you may relicense the work +under the MIT or the Modified BSD License, as listed at the following site: + +http://www.gnu.org/licenses/license-list.html diff --git a/autistdraw.c b/autistdraw.c new file mode 100644 index 0000000..ace5e14 --- /dev/null +++ b/autistdraw.c @@ -0,0 +1,232 @@ +// <poll.h> might need this for sigset_t +#define _XOPEN_SOURCE 600 + +#include <stdio.h> +#include <unistd.h> +#include <locale.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> + +#include <poll.h> +#include <signal.h> + +#include <curses.h> +#include "termo.h" + +#include "config.h" + +typedef struct app_data app_data_t; +struct app_data +{ + termo_t *tk; + + // Current attributes for the left mouse button + int current_attrs_left; + // Current attributes for the right mouse button + int current_attrs_right; +}; + +static int g_winch_pipe[2]; + +static void +display (const char *format, ...) +{ + va_list ap; + + mvwhline (stdscr, 0, 0, A_REVERSE, COLS); + attron (A_REVERSE); + + va_start (ap, format); + vw_printw (stdscr, format, ap); + va_end (ap); + + attroff (A_REVERSE); + refresh (); +} + +static void +init_palette (app_data_t *app) +{ + start_color (); + + // Also does init_pair (0, -1, -1); + use_default_colors (); + // Duplicate it for convenience. + init_pair (9, -1, -1); + + // Add the basic 8 colors to the default pair. Once normally, once + // inverted to workaround VTE's inability to set a bright background. + for (int i = 0; i < 8; i++) + { + init_pair (1 + i, COLOR_WHITE, COLOR_BLACK + i); + init_pair (10 + i, COLOR_BLACK + i, COLOR_WHITE); + } + + // This usually creates a solid black or white. + app->current_attrs_left = + app->current_attrs_right = COLOR_PAIR (9) | A_REVERSE | A_BOLD; +} + +static void +redraw (void) +{ + int i; + + mvwhline (stdscr, 1, 0, A_REVERSE, COLS); + mvwhline (stdscr, 2, 0, A_REVERSE, COLS); + + for (i = 0; i < COLS; i++) + { + int pair = (float) i / COLS * 9; + mvaddch (1, i, ' ' | COLOR_PAIR (pair)); + mvaddch (2, i, ' ' | COLOR_PAIR (pair + 9) | A_REVERSE | A_BOLD); + } + + display ("Choose a color from the palette and draw. " + "Press Escape or ^C to quit."); + refresh (); +} + +static bool +on_key (app_data_t *app, termo_key_t *key) +{ + if (key->type == TERMO_TYPE_KEYSYM + && key->code.sym == TERMO_SYM_ESCAPE) + return false; + + if (key->type == TERMO_TYPE_KEY + && (key->modifiers & TERMO_KEYMOD_CTRL) + && (key->code.codepoint == 'C' || key->code.codepoint == 'c')) + return false; + + if (key->type != TERMO_TYPE_MOUSE) + return true; + + int line, col, button; + termo_mouse_event_t event; + + termo_interpret_mouse (app->tk, key, &event, &button, &line, &col); + if (event != TERMO_MOUSE_PRESS && event != TERMO_MOUSE_DRAG) + return true; + + int *attrs; + if (button == 1) + attrs = &app->current_attrs_left; + else if (button == 3) + attrs = &app->current_attrs_right; + else + return true; + + chtype ch = mvwinch (stdscr, line, col); + if (line >= 3) + { + // Paste the attributes where the user clicked. + addch (' ' | *attrs); + refresh (); + } + else if (line > 0) + // Copy attributes from the pallete. + *attrs = ch & (A_COLOR | A_ATTRIBUTES); + + return true; +} + +static void +winch_handler (int signum) +{ + (void) signum; + write (g_winch_pipe[1], "x", 1); +} + +int +main (int argc, char *argv[]) +{ + (void) argc; + (void) argv; + + TERMO_CHECK_VERSION; + setlocale (LC_CTYPE, ""); + + struct sigaction act; + act.sa_handler = winch_handler; + act.sa_flags = SA_RESTART; + sigemptyset (&act.sa_mask); + + // Set up a self-pipe so that we can actually poll on SIGWINCH + if (sigaction (SIGWINCH, &act, NULL) || pipe (g_winch_pipe)) + { + fprintf (stderr, "Cannot set up signal handler\n"); + exit (EXIT_FAILURE); + } + + termo_t *tk = termo_new (STDIN_FILENO, NULL, 0); + if (!tk) + { + fprintf (stderr, "Cannot allocate termo instance\n"); + exit (EXIT_FAILURE); + } + + termo_set_mouse_proto (tk, termo_guess_mouse_proto (tk)); + termo_set_mouse_tracking_mode (tk, TERMO_MOUSE_TRACKING_DRAG); + + // Set up curses for our drawing needs + if (!initscr () || nonl () == ERR || curs_set (0) == ERR) + { + fprintf (stderr, "Cannot initialize curses\n"); + exit (EXIT_FAILURE); + } + + app_data_t app; + memset (&app, 0, sizeof app); + app.tk = tk; + + init_palette (&app); + redraw (); + + termo_result_t ret; + termo_key_t key; + + // We listen for mouse/key input and terminal resize events + struct pollfd fds[2] = + { + { .fd = STDIN_FILENO, .events = POLLIN }, + { .fd = g_winch_pipe[0], .events = POLLIN }, + }; + + // Run a simple event loop with poll() + int nextwait = -1; + bool running = true; + while (running) + { + if (!poll (fds, 2, nextwait)) + if (termo_getkey_force (tk, &key) == TERMO_RES_KEY) + running &= on_key (&app, &key); + + if (fds[1].revents & (POLLIN | POLLHUP | POLLERR)) + { + char x; + read (fds[1].fd, &x, 1); + + // The "official" simple and flicker-prone method of resizing + // the internal buffers of curses + endwin (); + refresh (); + + redraw (); + } + if (fds[0].revents & (POLLIN | POLLHUP | POLLERR)) + termo_advisereadable (tk); + + while ((ret = termo_getkey (tk, &key)) == TERMO_RES_KEY) + running &= on_key (&app, &key); + + nextwait = -1; + if (ret == TERMO_RES_AGAIN) + nextwait = termo_get_waittime (tk); + } + + endwin (); + termo_destroy (tk); +} + diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..2e0b129 --- /dev/null +++ b/config.h.in @@ -0,0 +1,11 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#define PROJECT_NAME "${CMAKE_PROJECT_NAME}" +#define PROJECT_VERSION "${project_VERSION}" +#define PROJECT_URL "${project_URL}" + +#cmakedefine HAVE_RESIZE_TERM + +#endif // ! CONFIG_H + |