Mercurial > x11calc
changeset 0:12567e0a7670 default tip
initial commit
| author | Yasuri <yasuri@charliesmp.net> |
|---|---|
| date | Sun, 04 Oct 2026 12:56:43 -0500 |
| parents | |
| children | |
| files | .hgignore LICENSE Makefile README calculate.cpp calculate.h config.h drw.c drw.h main.cpp shell.nix util.c util.h x11.cpp x11.h |
| diffstat | 15 files changed, 860 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Sun Oct 04 12:56:43 2026 -0500 @@ -0,0 +1,6 @@ +syntax: glob +*.o +*.d +calcx11 +.nix-cross-bin +calc
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LICENSE Sun Oct 04 12:56:43 2026 -0500 @@ -0,0 +1,48 @@ +ISC License, covers main.cpp, calculate.*, x11.*, config.h +ISC License + +Copyright 2026 Yasuri <yasuri@charliesmp.net> + +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. + +MIT/X Consortium License, covers drw.c, drw.h, util.c, util.h +MIT/X Consortium License + +© 2010-2026 Hiltjo Posthuma <hiltjo@codemadness.org> +© 2006-2019 Anselm R Garbe <anselm@garbe.ca> +© 2006-2009 Jukka Salmi <jukka at salmi dot ch> +© 2006-2007 Sander van Dijk <a dot h dot vandijk at gmail dot com> +© 2007-2011 Peter Hartlich <sgkkr at hartlich dot com> +© 2007-2009 Szabolcs Nagy <nszabolcs at gmail dot com> +© 2007-2009 Christof Musik <christof at sendfax dot de> +© 2007-2009 Premysl Hruby <dfenze at gmail dot com> +© 2007-2008 Enno Gottox Boland <gottox at s01 dot de> +© 2008 Martin Hurton <martin dot hurton at gmail dot com> +© 2008 Neale Pickett <neale dot woozle dot org> +© 2009 Mate Nagy <mnagy at port70 dot net> +© 2010-2012 Connor Lane Smith <cls@lubutu.com> +© 2011 Christoph Lohmann <20h@r-36.net> +© 2015-2016 Quentin Rameau <quinq@fifth.space> +© 2015-2016 Eric Pruitt <eric.pruitt@gmail.com> +© 2016-2017 Markus Teich <markus.teich@stusta.mhn.de> +© 2020-2022 Chris Down <chris@chrisdown.name> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Sun Oct 04 12:56:43 2026 -0500 @@ -0,0 +1,22 @@ +CXXFLAGS = -g $(shell pkg-config --cflags x11 xft fontconfig) +LIBS = $(shell pkg-config --libs x11 xft fontconfig) + +OBJ = main.o calculate.o x11.o drw.o util.o + +calcx11: $(OBJ) + $(CXX) -o $@ $(OBJ) $(LIBS) + +main.o: main.cpp config.h + $(CXX) $(CXXFLAGS) -c main.cpp + +drw.o: drw.c + $(CC) $(CFLAGS) $(shell pkg-config --cflags xft fontconfig) -c drw.c + +util.o: util.c + $(CC) $(CFLAGS) -c util.c + +config.h: + cp config.def.h config.h + +clean: + rm -f calcx11 $(OBJ)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README Sun Oct 04 12:56:43 2026 -0500 @@ -0,0 +1,15 @@ +x11calc +======= +A semi-modern calculator for X11, ~180 LOC + dwm's drw +Based on Xlib+Xft, uses drawing components from dwm (thanks suckless!) + +License +======= +ISC (for my code), MIT (for dwm code) +See LICENSE for details. + +Build +===== +Run make. +Dependencies: libX11, libXft, fontconfig, a C++11/C99 compiler +Build has been tested on NixOS (see shell.nix for a nix-shell with the build deps)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/calculate.cpp Sun Oct 04 12:56:43 2026 -0500 @@ -0,0 +1,20 @@ +#include <stdexcept> +#include "calculate.h" +double calculate(double x, double y, char op) { + switch(op) { + default: + throw std::runtime_error("unknown sign"); + case '*': + return x * y; + case '+': + return x + y; + case '-': + return x - y; + case '/': + if (y == 0) { + throw std::runtime_error("cannot divide by 0"); + } + return x / y; + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/calculate.h Sun Oct 04 12:56:43 2026 -0500 @@ -0,0 +1,3 @@ +#pragma once +double calculate(double x, double y, char op); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/config.h Sun Oct 04 12:56:43 2026 -0500 @@ -0,0 +1,15 @@ +static const char* fonts[] = { "Iosevka Nerd Font Propo:size=12" }; + +static const char* btnColors[] = { "#ffffff", "#444444", "#444444" }; // fg, bg, border +static const char* dispColors[] = { "#ffffff", "#000000", "#000000" }; + +static const int winW = 500, winH = 500; +static const int btnW = 100, btnH = 80, gap = 10; +static const int gridX = 10, gridY = 100; + +static const char* labels[4][4] = { + {"7","8","9","/"}, + {"4","5","6","*"}, + {"1","2","3","-"}, + {"0","C","=","+"} +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/drw.c Sun Oct 04 12:56:43 2026 -0500 @@ -0,0 +1,471 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <X11/Xlib.h> +#include <X11/Xft/Xft.h> + +#include "drw.h" +#include "util.h" + +#define UTF_INVALID 0xFFFD + +static int +utf8decode(const char *s_in, long *u, int *err) +{ + static const unsigned char lens[] = { + /* 0XXXX */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + /* 10XXX */ 0, 0, 0, 0, 0, 0, 0, 0, /* invalid */ + /* 110XX */ 2, 2, 2, 2, + /* 1110X */ 3, 3, + /* 11110 */ 4, + /* 11111 */ 0, /* invalid */ + }; + static const unsigned char leading_mask[] = { 0x7F, 0x1F, 0x0F, 0x07 }; + static const unsigned int overlong[] = { 0x0, 0x80, 0x0800, 0x10000 }; + + const unsigned char *s = (const unsigned char *)s_in; + int len = lens[*s >> 3]; + *u = UTF_INVALID; + *err = 1; + if (len == 0) + return 1; + + long cp = s[0] & leading_mask[len - 1]; + for (int i = 1; i < len; ++i) { + if (s[i] == '\0' || (s[i] & 0xC0) != 0x80) + return i; + cp = (cp << 6) | (s[i] & 0x3F); + } + /* out of range, surrogate, overlong encoding */ + if (cp > 0x10FFFF || (cp >> 11) == 0x1B || cp < overlong[len - 1]) + return len; + + *err = 0; + *u = cp; + return len; +} + +Drw * +drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) +{ + Drw *drw = ecalloc(1, sizeof(Drw)); + + drw->dpy = dpy; + drw->screen = screen; + drw->root = root; + drw->w = w; + drw->h = h; + drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); + drw->gc = XCreateGC(dpy, root, 0, NULL); + XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); + + return drw; +} + +void +drw_resize(Drw *drw, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + drw->w = w; + drw->h = h; + if (drw->drawable) + XFreePixmap(drw->dpy, drw->drawable); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); +} + +void +drw_free(Drw *drw) +{ + XFreePixmap(drw->dpy, drw->drawable); + XFreeGC(drw->dpy, drw->gc); + drw_fontset_free(drw->fonts); + free(drw); +} + +/* This function is an implementation detail. Library users should use + * drw_fontset_create instead. + */ +static Fnt * +xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) +{ + Fnt *font; + XftFont *xfont = NULL; + FcPattern *pattern = NULL; + + if (fontname) { + /* Using the pattern found at font->xfont->pattern does not yield the + * same substitution results as using the pattern returned by + * FcNameParse; using the latter results in the desired fallback + * behaviour whereas the former just results in missing-character + * rectangles being drawn, at least with some fonts. */ + if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { + fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); + return NULL; + } + if (!(pattern = FcNameParse((FcChar8 *) fontname))) { + fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); + XftFontClose(drw->dpy, xfont); + return NULL; + } + } else if (fontpattern) { + if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { + fprintf(stderr, "error, cannot load font from pattern.\n"); + return NULL; + } + } else { + die("no font specified."); + } + + font = ecalloc(1, sizeof(Fnt)); + font->xfont = xfont; + font->pattern = pattern; + font->h = xfont->ascent + xfont->descent; + font->dpy = drw->dpy; + + return font; +} + +static void +xfont_free(Fnt *font) +{ + if (!font) + return; + if (font->pattern) + FcPatternDestroy(font->pattern); + XftFontClose(font->dpy, font->xfont); + free(font); +} + +Fnt* +drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) +{ + Fnt *cur, *ret = NULL; + size_t i; + + if (!drw || !fonts) + return NULL; + + for (i = 1; i <= fontcount; i++) { + if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { + cur->next = ret; + ret = cur; + } + } + return (drw->fonts = ret); +} + +void +drw_fontset_free(Fnt *font) +{ + if (font) { + drw_fontset_free(font->next); + xfont_free(font); + } +} + +void +drw_clr_create(Drw *drw, Clr *dest, const char *clrname) +{ + if (!drw || !dest || !clrname) + return; + + if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen), + clrname, dest)) + die("error, cannot allocate color '%s'", clrname); +} + +/* Create color schemes. */ +Clr * +drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) +{ + size_t i; + Clr *ret; + + /* need at least two colors for a scheme */ + if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(Clr)))) + return NULL; + + for (i = 0; i < clrcount; i++) + drw_clr_create(drw, &ret[i], clrnames[i]); + return ret; +} + +void +drw_clr_free(Drw *drw, Clr *c) +{ + if (!drw || !c) + return; + + /* c is typedef XftColor Clr */ + XftColorFree(drw->dpy, DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen), c); +} + +void +drw_scm_free(Drw *drw, Clr *scm, size_t clrcount) +{ + size_t i; + + if (!drw || !scm) + return; + + for (i = 0; i < clrcount; i++) + drw_clr_free(drw, &scm[i]); + free(scm); +} + +void +drw_setfontset(Drw *drw, Fnt *set) +{ + if (drw) + drw->fonts = set; +} + +void +drw_setscheme(Drw *drw, Clr *scm) +{ + if (drw) + drw->scheme = scm; +} + +void +drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) +{ + if (!drw || !drw->scheme) + return; + XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); + if (filled) + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + else + XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); +} + +int +drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) +{ + int ty, ellipsis_x = 0; + unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, hash, h0, h1; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + int utf8strlen, utf8charlen, utf8err, render = x || y || w || h; + long utf8codepoint = 0; + const char *utf8str; + FcCharSet *fccharset; + FcPattern *fcpattern; + FcPattern *match; + XftResult result; + int charexists = 0, overflow = 0; + /* keep track of a couple codepoints for which we have no match. */ + static unsigned int nomatches[128], ellipsis_width, invalid_width; + static const char invalid[] = "�"; + + if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) + return 0; + + if (!render) { + w = invert ? invert : ~invert; + } else { + XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + if (w < lpad) + return x + w; + d = XftDrawCreate(drw->dpy, drw->drawable, + DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen)); + x += lpad; + w -= lpad; + } + + usedfont = drw->fonts; + if (!ellipsis_width && render) + ellipsis_width = drw_fontset_getwidth(drw, "..."); + if (!invalid_width && render) + invalid_width = drw_fontset_getwidth(drw, invalid); + while (1) { + ew = ellipsis_len = utf8err = utf8charlen = utf8strlen = 0; + utf8str = text; + nextfont = NULL; + while (*text) { + utf8charlen = utf8decode(text, &utf8codepoint, &utf8err); + for (curfont = drw->fonts; curfont; curfont = curfont->next) { + charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); + if (charexists) { + drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL); + if (ew + ellipsis_width <= w) { + /* keep track where the ellipsis still fits */ + ellipsis_x = x + ew; + ellipsis_w = w - ew; + ellipsis_len = utf8strlen; + } + + if (ew + tmpw > w) { + overflow = 1; + /* called from drw_fontset_getwidth_clamp(): + * it wants the width AFTER the overflow + */ + if (!render) + x += tmpw; + else + utf8strlen = ellipsis_len; + } else if (curfont == usedfont) { + text += utf8charlen; + utf8strlen += utf8err ? 0 : utf8charlen; + ew += utf8err ? 0 : tmpw; + } else { + nextfont = curfont; + } + break; + } + } + + if (overflow || !charexists || nextfont || utf8err) + break; + else + charexists = 0; + } + + if (utf8strlen) { + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen); + } + x += ew; + w -= ew; + } + if (utf8err && (!render || invalid_width < w)) { + if (render) + drw_text(drw, x, y, w, h, 0, invalid, invert); + x += invalid_width; + w -= invalid_width; + } + if (render && overflow) + drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert); + + if (!*text || overflow) { + break; + } else if (nextfont) { + charexists = 0; + usedfont = nextfont; + } else { + /* Regardless of whether or not a fallback font is found, the + * character must be drawn. */ + charexists = 1; + + hash = (unsigned int)utf8codepoint; + hash = ((hash >> 16) ^ hash) * 0x21F0AAAD; + hash = ((hash >> 15) ^ hash) * 0xD35A2D97; + h0 = ((hash >> 15) ^ hash) % LENGTH(nomatches); + h1 = (hash >> 17) % LENGTH(nomatches); + /* avoid expensive XftFontMatch call when we know we won't find a match */ + if (nomatches[h0] == utf8codepoint || nomatches[h1] == utf8codepoint) + goto no_match; + + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, utf8codepoint); + + if (!drw->fonts->pattern) { + /* Refer to the comment in xfont_create for more information. */ + die("the first font in the cache must be loaded from a font string."); + } + + fcpattern = FcPatternDuplicate(drw->fonts->pattern); + FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); + + FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); + + FcCharSetDestroy(fccharset); + FcPatternDestroy(fcpattern); + + if (match) { + usedfont = xfont_create(drw, NULL, match); + if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { + for (curfont = drw->fonts; curfont->next; curfont = curfont->next) + ; /* NOP */ + curfont->next = usedfont; + } else { + xfont_free(usedfont); + nomatches[nomatches[h0] ? h1 : h0] = utf8codepoint; +no_match: + usedfont = drw->fonts; + } + } + } + } + if (d) + XftDrawDestroy(d); + + return x + (render ? w : 0); +} + +void +drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); + XSync(drw->dpy, False); +} + +unsigned int +drw_fontset_getwidth(Drw *drw, const char *text) +{ + if (!drw || !drw->fonts || !text) + return 0; + return drw_text(drw, 0, 0, 0, 0, 0, text, 0); +} + +unsigned int +drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n) +{ + unsigned int tmp = 0; + if (drw && drw->fonts && text && n) + tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n); + return MIN(n, tmp); +} + +void +drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) +{ + XGlyphInfo ext; + + if (!font || !text) + return; + + XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); + if (w) + *w = ext.xOff; + if (h) + *h = font->h; +} + +Cur * +drw_cur_create(Drw *drw, int shape) +{ + Cur *cur; + + if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) + return NULL; + + cur->cursor = XCreateFontCursor(drw->dpy, shape); + + return cur; +} + +void +drw_cur_free(Drw *drw, Cur *cursor) +{ + if (!cursor) + return; + + XFreeCursor(drw->dpy, cursor->cursor); + free(cursor); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/drw.h Sun Oct 04 12:56:43 2026 -0500 @@ -0,0 +1,60 @@ +/* See LICENSE file for copyright and license details. */ + +typedef struct { + Cursor cursor; +} Cur; + +typedef struct Fnt { + Display *dpy; + unsigned int h; + XftFont *xfont; + FcPattern *pattern; + struct Fnt *next; +} Fnt; + +enum { ColFg, ColBg, ColBorder }; /* Clr scheme index */ +typedef XftColor Clr; + +typedef struct { + unsigned int w, h; + Display *dpy; + int screen; + Window root; + Drawable drawable; + GC gc; + Clr *scheme; + Fnt *fonts; +} Drw; + +/* Drawable abstraction */ +Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); +void drw_resize(Drw *drw, unsigned int w, unsigned int h); +void drw_free(Drw *drw); + +/* Fnt abstraction */ +Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); +void drw_fontset_free(Fnt* set); +unsigned int drw_fontset_getwidth(Drw *drw, const char *text); +unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n); +void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); + +/* Colorscheme abstraction */ +void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); +void drw_clr_free(Drw *drw, Clr *c); +Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); +void drw_scm_free(Drw *drw, Clr *scm, size_t clrcount); + +/* Cursor abstraction */ +Cur *drw_cur_create(Drw *drw, int shape); +void drw_cur_free(Drw *drw, Cur *cursor); + +/* Drawing context manipulation */ +void drw_setfontset(Drw *drw, Fnt *set); +void drw_setscheme(Drw *drw, Clr *scm); + +/* Drawing functions */ +void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); +int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); + +/* Map functions */ +void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.cpp Sun Oct 04 12:56:43 2026 -0500 @@ -0,0 +1,126 @@ +#include <iostream> +#include <stdexcept> +#include <vector> +#include <string> +#include <sstream> +#include <cstring> +#include <unistd.h> +#include <X11/Xlib.h> +#include <X11/Xft/Xft.h> +#include <fontconfig/fontconfig.h> +#include <X11/keysym.h> +#include "calculate.h" +#include "x11.h" + +extern "C" { +#include "drw.h" +} +#include "config.h" + +struct Button { int x, y, w, h; std::string label; }; + +std::vector<Button> makeButtons() { + std::vector<Button> b; + for (int r = 0; r < 4; r++) + for (int c = 0; c < 4; c++) + b.push_back({gridX + c*(btnW+gap), gridY+ r*(btnH+gap), btnW, btnH, labels[r][c]}); + return b; +} + +void redraw(Drw* drw, Clr* btnScheme, Clr* dispScheme, + const std::vector<Button>& btns, const std::string& input) { + // display area (also paints the pixmap's background) + drw_setscheme(drw, dispScheme); + drw_rect(drw, 0, 0, 500, 500, 1, 1); + drw_text(drw, 10, 10, 480, 70, 10, input.c_str(), 0); + + // buttons + drw_setscheme(drw, btnScheme); + for (const auto& b : btns) { + unsigned int tw = drw_fontset_getwidth(drw, b.label.c_str()); + int lpad = (b.w - (int)tw) / 2; + drw_text(drw, b.x, b.y, b.w, b.h, lpad, b.label.c_str(), 0); + } +} + +int main() { + Display *dpy = displayopen(); + int screen = DefaultScreen(dpy); + Window root = RootWindow(dpy, screen); + Window win = XCreateSimpleWindow(dpy, root, 0, 0, 500, 500, 1, 0xFFFFFF, 0x000000); + XStoreName(dpy, win, "Calculator"); + XSelectInput(dpy, win, ExposureMask | KeyPressMask | ButtonPressMask); + XMapWindow(dpy, win); + + Atom wmDelete = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + XSetWMProtocols(dpy, win, &wmDelete, 1); + + Drw* drw = drw_create(dpy, screen, root, 500, 500); + if (!drw) { std::cerr << "drw_create failed\n"; return 1; } + + if (!drw_fontset_create(drw, fonts, 1)) { + std::cerr << "font load failed\n"; + return 1; + } + + Clr* btnScheme = drw_scm_create(drw, btnColors, 3); + Clr* dispScheme = drw_scm_create(drw, dispColors, 3); + + std::vector<Button> btns = makeButtons(); + std::string input; + XEvent ev; + bool running = true; + + while (running) { + XNextEvent(dpy, &ev); + switch (ev.type) { + case ClientMessage: + if ((Atom)ev.xclient.data.l[0] == wmDelete) running = false; + break; + case Expose: + if (ev.xexpose.count == 0) { + redraw(drw, btnScheme, dispScheme, btns, input); + drw_map(drw, win, 0, 0, winW, winH); + } + break; + case KeyPress: { + KeySym k = XLookupKeysym(&ev.xkey, 0); + if (k == XK_q) running = false; + break; + } + case ButtonPress: + if (ev.xbutton.button != Button1) break; + for (const auto& b : btns) { + bool hit = ev.xbutton.x >= b.x && ev.xbutton.x < b.x + b.w && + ev.xbutton.y >= b.y && ev.xbutton.y < b.y + b.h; + if (!hit) continue; + + if (b.label == "=") { + size_t pos = input.find_first_of("+-*/", 1); + if (pos != std::string::npos) { + try { + double x = std::stod(input.substr(0, pos)); + double y = std::stod(input.substr(pos + 1)); + char op = input[pos]; + std::ostringstream ss; + ss << calculate(x, y, op); + input = ss.str(); + } catch (const std::exception& e) { + input = e.what(); + } + } + } else if (b.label == "C") { + input.clear(); + } else { + input += b.label; + } + XClearArea(dpy, win, 0, 0, 0, 0, True); + break; + } + break; + } + } + + drw_free(drw); + XCloseDisplay(dpy); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/shell.nix Sun Oct 04 12:56:43 2026 -0500 @@ -0,0 +1,12 @@ +{ pkgs ? import <nixpkgs> {} }: + +pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + gcc + pkg-config + ]; + buildInputs = with pkgs; [ + libX11 + libXft + ]; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/util.c Sun Oct 04 12:56:43 2026 -0500 @@ -0,0 +1,37 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +void +die(const char *fmt, ...) +{ + va_list ap; + int saved_errno; + + saved_errno = errno; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') + fprintf(stderr, " %s", strerror(saved_errno)); + fputc('\n', stderr); + + exit(1); +} + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/util.h Sun Oct 04 12:56:43 2026 -0500 @@ -0,0 +1,9 @@ +/* See LICENSE file for copyright and license details. */ + +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) +#define LENGTH(X) (sizeof (X) / sizeof (X)[0]) + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/x11.cpp Sun Oct 04 12:56:43 2026 -0500 @@ -0,0 +1,13 @@ +#include <X11/Xlib.h> +#include <iostream> +#include <cstdlib> +#include "x11.h" +Display *displayopen() +{ + Display* dpy = XOpenDisplay(NULL); + if (dpy == NULL) { + std::cerr << "Could not open display\n"; + std::exit(1); + } + return dpy; +}
