1495 lines · 42.5 KB · C++
RawDownload
/* This file is part of Zutty. * Copyright (C) 2020 Tom Szilagyi * * 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 3 of the License, or * (at your option) any later version. * * See the file LICENSE for the full license. */ #include <X11/Xlib.h>#include <X11/Xatom.h>#include <X11/Xutil.h>#include <X11/keysym.h>#include <X11/Xmu/Error.h> #include "base.h"#include "base64.h"#include "fontpack.h"#include "options.h"#include "pty.h"#include "renderer.h"#include "selmgr.h"#include "vterm.h"#include "wm_icons.h" #include <cassert>#include <langinfo.h>#include <memory>#include <poll.h>#include <pwd.h>#include <signal.h>#include <sstream>#include <stdlib.h>#include <string.h>#include <sys/stat.h>#include <sys/types.h>#include <sys/wait.h> using zutty::Fontpack;using zutty::MouseTrackingState;using zutty::MouseTrackingMode;using zutty::MouseTrackingEnc;using zutty::Vterm;using zutty::VtKey;using zutty::VtModifier;using zutty::Renderer;using zutty::SelectionManager; static std::unique_ptr <Fontpack> fontpk = nullptr;static std::unique_ptr <Renderer> renderer = nullptr;static std::unique_ptr <Vterm> vt = nullptr;static std::unique_ptr <SelectionManager> selMgr = nullptr; static Display* xDisplay = nullptr;static Window xWindow;static Atom wmDeleteMessage;static XWMHints wmHints;static XSizeHints sizeHints;static Colormap colormap; static voidconvertColor (const zutty::Color& color, XColor& xcolor){   std::ostringstream oss;   oss << color;   if (XParseColor (xDisplay, colormap, oss.str ().c_str (), &xcolor) == 0)   {      logE << "XParseColor (): could not resolve " << oss.str () << std::endl;   }} static voidsetXCursor (){   const static int cursorW = 9;   const static int cursorH = 16;   const static int cursorHotX = 4;   const static int cursorHotY = 7;   const static unsigned char cursorBits [] =   {      0x00, 0x00, 0xee, 0x00, 0x38, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00,      0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00,      0x10, 0x00, 0x38, 0x00, 0xee, 0x00, 0x00, 0x00   };   const static unsigned char cursorMaskBits [] =   {      0xef, 0x01, 0xff, 0x01, 0xff, 0x01, 0x7c, 0x00, 0x38, 0x00, 0x38, 0x00,      0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x00,      0x7c, 0x00, 0xff, 0x01, 0xff, 0x01, 0xef, 0x01   };    XColor xc_fg;   convertColor (opts.fg, xc_fg);   XColor xc_bg;   convertColor (opts.bg, xc_bg);   Window root = DefaultRootWindow (xDisplay);   Pixmap pxcur = XCreateBitmapFromData (      xDisplay, root, (char *)cursorBits, cursorW, cursorH);   Pixmap pxmask = XCreateBitmapFromData(      xDisplay, root, (char *)cursorMaskBits, cursorW, cursorH);   Cursor cursor = XCreatePixmapCursor(      xDisplay, pxcur, pxmask, &xc_fg, &xc_bg, cursorHotX, cursorHotY);   XFreePixmap(xDisplay, pxcur);   XFreePixmap(xDisplay, pxmask);   XRecolorCursor (xDisplay, cursor, &xc_fg, &xc_bg);   XDefineCursor (xDisplay, xWindow, cursor);} static voidsetUtf8prop (const char* prop_name, const std::string & value){   Atom utf8 = XInternAtom (xDisplay, "UTF8_STRING", false);   Atom prop = XInternAtom (xDisplay, prop_name, false);   XChangeProperty (xDisplay, xWindow, prop, utf8, 8, PropModeReplace,                    (const unsigned char*) value.data (), value.size ());} static voidsetXWindowName (const std::string & name){   XStoreName (xDisplay, xWindow, name.c_str ());   setUtf8prop ("_NET_WM_NAME", name);} static voidsetXWindowIconName (const std::string & name){   XSetIconName (xDisplay, xWindow, name.c_str ());   setUtf8prop ("_NET_WM_ICON_NAME", name);} static voidmakeXWindow (const char* name, int width, int height, int px, int py,             EGLDisplay eglDpy, EGLContext& eglCtx, EGLSurface& eglSurface){   static const EGLint eglAttrs [] = {      EGL_RED_SIZE, 8,      EGL_GREEN_SIZE, 8,      EGL_BLUE_SIZE, 8,      EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,      EGL_NONE   };   static const EGLint ctxAttrs [] = {      EGL_CONTEXT_CLIENT_VERSION, 2,      EGL_NONE   };    XSetWindowAttributes attr;   unsigned long mask;   Window root;   XVisualInfo *visInfo, visTemplate;   int numVisuals;   EGLConfig config;   EGLint numConfigs;   EGLint vid;    root = RootWindow (xDisplay, DefaultScreen (xDisplay));    if (!eglChooseConfig (eglDpy, eglAttrs, &config, 1, &numConfigs))   {      logE << "Couldn't get an EGL visual config" << std::endl;      exit(1);   }    assert (config);   assert (numConfigs > 0);    if (!eglGetConfigAttrib (eglDpy, config, EGL_NATIVE_VISUAL_ID, &vid))   {      logE << "eglGetConfigAttrib() failed" << std::endl;      exit (1);   }    // The X window visual must match the EGL config   visTemplate.visualid = vid;   visInfo = XGetVisualInfo (xDisplay, VisualIDMask, &visTemplate, &numVisuals);   if (!visInfo) {      logE << "Couldn't get X visual" << std::endl;      exit (1);   }    // window attributes   colormap = XCreateColormap (xDisplay, root, visInfo->visual, AllocNone);   attr.background_pixel = 0;   attr.border_pixel = 0;   attr.colormap = colormap;   attr.event_mask = StructureNotifyMask | ExposureMask | FocusChangeMask |      PropertyChangeMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask |      PointerMotionMask;   mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;    xWindow = XCreateWindow (xDisplay, root, 0, 0, width, height,                            0, visInfo->depth, InputOutput,                            visInfo->visual, mask, &attr);   logI << "Window ID: " << xWindow << " / 0x" << std::hex << xWindow        << std::dec << std::endl;   if (setenv ("WINDOWID", std::to_string (xWindow).c_str (), 1) < 0)      SYS_ERROR ("setenv (WINDOWID)");    {      XClassHint classHint;      classHint.res_name = (char *)opts.name;      classHint.res_class = (char *)"Zutty";       wmHints.input = true;      wmHints.initial_state = NormalState;      wmHints.flags = InputHint | StateHint;       sizeHints.width = width;      sizeHints.height = height;      sizeHints.base_width = 2 * opts.border;      sizeHints.base_height = 2 * opts.border;      sizeHints.min_width = 2 * opts.border + px;      sizeHints.min_height = 2 * opts.border + py;      sizeHints.width_inc = px;      sizeHints.height_inc = py;      sizeHints.win_gravity = NorthWestGravity;      sizeHints.flags = PSize | PMinSize | PBaseSize | PResizeInc | PWinGravity;       XmbSetWMProperties (xDisplay, xWindow, name, name,                          nullptr, 0, &sizeHints, &wmHints, &classHint);   }    {      std::string title {name};      setXWindowName (title);      setXWindowIconName (title);   }    {      // set NET_WM_PID to the process ID to link the window to the pid      Atom _NET_WM_PID = XInternAtom (xDisplay, "_NET_WM_PID", false);      pid_t pid = getpid ();      XChangeProperty (xDisplay, xWindow, _NET_WM_PID, XA_CARDINAL,                       32, PropModeReplace, (unsigned char *)&pid, 1);   }    {      // set NET_WM_ICON to wm_icons [] (16x16 and 32x32 32-bit cardinal ARGB)      Atom _NET_WM_ICON = XInternAtom (xDisplay, "_NET_WM_ICON", false);      XChangeProperty (xDisplay, xWindow, _NET_WM_ICON, XA_CARDINAL,                       32, PropModeReplace, (const unsigned char*) wm_icons,                       sizeof (wm_icons) / sizeof (wm_icons [0]));   }    wmDeleteMessage = XInternAtom (xDisplay, "WM_DELETE_WINDOW", False);   XSetWMProtocols (xDisplay, xWindow, &wmDeleteMessage, 1);    eglBindAPI (EGL_OPENGL_ES_API);    eglCtx = eglCreateContext (eglDpy, config, EGL_NO_CONTEXT, ctxAttrs);   if (!eglCtx)   {      logE << "eglCreateContext failed" << std::endl;      exit (1);   }    // test eglQueryContext()   {      EGLint val;      eglQueryContext (eglDpy, eglCtx, EGL_CONTEXT_CLIENT_TYPE, &val);      assert (val == EGL_OPENGL_ES_API);   }    eglSurface = eglCreateWindowSurface (eglDpy, config,                                        (EGLNativeWindowType)xWindow, nullptr);   if (! eglSurface) {      logE << "eglCreateWindowSurface failed" << std::endl;      exit (1);   }    // sanity checks   {      EGLint val;      eglQuerySurface (eglDpy, eglSurface, EGL_WIDTH, &val);      assert (val == width);      eglQuerySurface (eglDpy, eglSurface, EGL_HEIGHT, &val);      assert (val == height);      assert (eglGetConfigAttrib (eglDpy, config, EGL_SURFACE_TYPE, &val));      assert (val & EGL_WINDOW_BIT);   }    XFree (visInfo);    setXCursor ();} static voidresolveShell (char* progPath){   char resolvedPath [PATH_MAX];   if (progPath [0] == '/')      return; // absolute path; we are done   else if (progPath [0] == '.' &&            realpath (progPath, resolvedPath) != nullptr)   {      strncpy (progPath, resolvedPath, PATH_MAX);      return;   }    // go through PATH and try to resolve our program   char* PATH = strdup (getenv ("PATH"));   if (PATH)   {      char testPath [PATH_MAX+1];      char* p = strtok (PATH, ":");      while (p)      {         snprintf (testPath, PATH_MAX+1, "%s/%s", p, progPath);         if (realpath (testPath, resolvedPath) != nullptr)         {            strncpy (progPath, resolvedPath, PATH_MAX);            free (PATH);            return;         }         p = strtok (nullptr, ":");      }      free (PATH);   }    // look at $SHELL   char* SHELL = getenv ("SHELL");   struct stat statbuf;   if (SHELL && stat (SHELL, &statbuf) == 0 && (statbuf.st_mode & S_IXOTH))   {      strncpy (progPath, SHELL, PATH_MAX-1);      return;   }    // obtain user's shell from /etc/passwd   auto pwent = getpwuid (getuid ());   SHELL = pwent->pw_shell;   if (SHELL && stat (SHELL, &statbuf) == 0 && (statbuf.st_mode & S_IXOTH))   {      strncpy (progPath, SHELL, PATH_MAX-1);      return;   }    // last resort   strcpy (progPath, "/bin/sh");} static voidvalidateShell (char* progPath){   resolveShell (progPath);    // validate against entries in /etc/shells   char* permShell = getusershell ();   while (permShell)   {      if (strcmp (progPath, permShell) == 0)      {         endusershell ();         setenv ("SHELL", progPath, 1);         return;      }      permShell = getusershell ();   }   endusershell ();    // progPath is *not* one of the permitted user shells   unsetenv ("SHELL");} static voidsetArgv0 (char* argv0){   const char* shbase = strrchr (opts.shell, '/');   if (shbase)      ++shbase;   else      shbase = opts.shell;    if (opts.login)   {      argv0 [0] = '-';      strncpy (argv0 + 1, shbase, PATH_MAX-2);   }   else   {      strncpy (argv0, shbase, PATH_MAX-1);   }} static voidsighandler (int sig, siginfo_t* info, void* ucontext){   if (sig == SIGCHLD)   {      waitpid (info->si_pid, nullptr, 0);   }} static voidsetupSignals (){   // The SIGCHLD handler is required to detect that the child process   // has quit; its handler ensures we won't create zombies.   {      struct sigaction sa {};      sa.sa_sigaction = sighandler;      sa.sa_flags = SA_SIGINFO | SA_RESTART | SA_NOCLDSTOP;      if (sigaction (SIGCHLD, &sa, nullptr) < 0)         SYS_ERROR ("can't install SIGCHLD handler: sigaction()");   }    // SIGINT and SIGQUIT might have inherited handlers if Zutty was launched   // from an interactive Bash shell. Restore the default handlers to enable   // normal functionality (e.g., terminate a program under Zutty with Ctrl-C);   // see bash(1) section SIGNALS.   {      struct sigaction sa {};      sa.sa_handler = SIG_DFL;      sa.sa_flags = 0;      if (sigaction (SIGINT, &sa, nullptr) < 0)         SYS_ERROR ("can't reset SIGINT handler to SIG_DFL: sigaction()");      if (sigaction (SIGQUIT, &sa, nullptr) < 0)         SYS_ERROR ("can't reset SIGQUIT handler to SIG_DFL: sigaction()");   }} static intstartShell (const char* execPath, const char* const argv[]){   int ptyFd;   pid_t pid;    pid = zutty::pty_fork (ptyFd, opts.nCols, opts.nRows);    if (pid < 0)   {      SYS_ERROR ("fork");   }   else if (pid == 0) // child:   {      // N.B.: DISPLAY and WINDOWID are inherited from parent      // environment (set elsewhere)      if (setenv ("TERM", "xterm-256color", 1) < 0)         SYS_ERROR ("setenv TERM");       if (setenv ("COLORTERM", "truecolor", 1) < 0)         SYS_ERROR ("setenv COLORTERM");       if (execvp (execPath, (char * const *) argv) < 0)         SYS_ERROR ("execvp of ", execPath);   }   else // parent:   {      logT << "Shell subprocess started, pid: " << pid << std::endl;   }    return ptyFd;} static VtModifierconvertKeyState (KeySym ks, unsigned int state){   VtModifier mod = VtModifier::none;   if (state & ShiftMask)      mod = mod | VtModifier::shift;   if (state & ControlMask)      mod = mod | VtModifier::control;   if (state & Mod1Mask)      mod = mod | VtModifier::alt;   return mod;} static voidpasteCb (bool success, const std::string& content){   if (success)      vt->pasteSelection (content);} static boolonKeyPress (XEvent& event, XIC& xic, int ptyFd){   using Key = VtKey;   XKeyEvent& xkevt = event.xkey;    KeySym ks;   char buffer [16];   const int avail = sizeof (buffer) - 1;   int nbytes = 0;    if (xic)   {      Status status;      nbytes = XmbLookupString (xic, &xkevt, buffer, avail, &ks, &status);      if (status == XBufferOverflow) {         logE << "KeyPress event: buffer size " << sizeof (buffer)              << " is too small for XmbLookupString, would have needed "              << " a buffer with " << nbytes + 1 << " bytes."              << std::endl;         return true;      }   }

Lines 1–500 of 1495.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t