/* 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. Load more
/* 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 void convertColor (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 void setXCursor () { 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 void setUtf8prop (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 void setXWindowName (const std::string & name) { XStoreName (xDisplay, xWindow, name.c_str ()); setUtf8prop ("_NET_WM_NAME", name); } static void setXWindowIconName (const std::string & name) { XSetIconName (xDisplay, xWindow, name.c_str ()); setUtf8prop ("_NET_WM_ICON_NAME", name); } static void makeXWindow (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 void resolveShell (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 void validateShell (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 void setArgv0 (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 void sighandler (int sig, siginfo_t* info, void* ucontext) { if (sig == SIGCHLD) { waitpid (info->si_pid, nullptr, 0); } } static void setupSignals () { // 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 int startShell (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 VtModifier convertKeyState (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 void pasteCb (bool success, const std::string& content) { if (success) vt->pasteSelection (content); } static bool onKeyPress (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; } }