1 /*
2  * data-types.c
3  * Copyright (C) 2016 Kovid Goyal <kovid at kovidgoyal.net>
4  *
5  * Distributed under terms of the GPL3 license.
6  */
7 
8 #ifdef __APPLE__
9 // Needed for _CS_DARWIN_USER_CACHE_DIR
10 #define _DARWIN_C_SOURCE
11 #include <unistd.h>
12 #undef _DARWIN_C_SOURCE
13 #endif
14 
15 #include "data-types.h"
16 #include "cleanup.h"
17 #include "safe-wrappers.h"
18 #include "control-codes.h"
19 #include "wcwidth-std.h"
20 #include "wcswidth.h"
21 #include "modes.h"
22 #include <stddef.h>
23 #include <termios.h>
24 #include <signal.h>
25 #include <fcntl.h>
26 #include <stdio.h>
27 #include <locale.h>
28 
29 #ifdef WITH_PROFILER
30 #include <gperftools/profiler.h>
31 #endif
32 
33 #include "monotonic.h"
34 
35 #ifdef __APPLE__
36 #include <libproc.h>
37 #include <xlocale.h>
38 
39 static PyObject*
user_cache_dir()40 user_cache_dir() {
41     static char buf[1024];
42     if (!confstr(_CS_DARWIN_USER_CACHE_DIR, buf, sizeof(buf) - 1)) return PyErr_SetFromErrno(PyExc_OSError);
43     return PyUnicode_FromString(buf);
44 }
45 
46 static PyObject*
process_group_map()47 process_group_map() {
48     int num_of_processes = proc_listallpids(NULL, 0);
49     size_t bufsize = sizeof(pid_t) * (num_of_processes + 1024);
50     FREE_AFTER_FUNCTION pid_t *buf = malloc(bufsize);
51     if (!buf) return PyErr_NoMemory();
52     num_of_processes = proc_listallpids(buf, (int)bufsize);
53     PyObject *ans = PyTuple_New(num_of_processes);
54     if (ans == NULL) { return PyErr_NoMemory(); }
55     for (int i = 0; i < num_of_processes; i++) {
56         long pid = buf[i], pgid = getpgid(buf[i]);
57         PyObject *t = Py_BuildValue("ll", pid, pgid);
58         if (t == NULL) { Py_DECREF(ans); return NULL; }
59         PyTuple_SET_ITEM(ans, i, t);
60     }
61     return ans;
62 }
63 #endif
64 
65 static PyObject*
redirect_std_streams(PyObject UNUSED * self,PyObject * args)66 redirect_std_streams(PyObject UNUSED *self, PyObject *args) {
67     char *devnull = NULL;
68     if (!PyArg_ParseTuple(args, "s", &devnull)) return NULL;
69     if (freopen(devnull, "r", stdin) == NULL) return PyErr_SetFromErrno(PyExc_OSError);
70     if (freopen(devnull, "w", stdout) == NULL) return PyErr_SetFromErrno(PyExc_OSError);
71     if (freopen(devnull, "w", stderr) == NULL)  return PyErr_SetFromErrno(PyExc_OSError);
72     Py_RETURN_NONE;
73 }
74 
75 static PyObject*
pyset_iutf8(PyObject UNUSED * self,PyObject * args)76 pyset_iutf8(PyObject UNUSED *self, PyObject *args) {
77     int fd, on;
78     if (!PyArg_ParseTuple(args, "ip", &fd, &on)) return NULL;
79     if (!set_iutf8(fd, on & 1)) return PyErr_SetFromErrno(PyExc_OSError);
80     Py_RETURN_NONE;
81 }
82 
83 #ifdef WITH_PROFILER
84 static PyObject*
start_profiler(PyObject UNUSED * self,PyObject * args)85 start_profiler(PyObject UNUSED *self, PyObject *args) {
86     char *path;
87     if (!PyArg_ParseTuple(args, "s", &path)) return NULL;
88     ProfilerStart(path);
89     Py_RETURN_NONE;
90 }
91 
92 static PyObject*
stop_profiler(PyObject UNUSED * self,PyObject * args UNUSED)93 stop_profiler(PyObject UNUSED *self, PyObject *args UNUSED) {
94     ProfilerStop();
95     Py_RETURN_NONE;
96 }
97 #endif
98 
99 static bool
put_tty_in_raw_mode(int fd,const struct termios * termios_p,bool read_with_timeout,int optional_actions)100 put_tty_in_raw_mode(int fd, const struct termios* termios_p, bool read_with_timeout, int optional_actions) {
101     struct termios raw_termios = *termios_p;
102     cfmakeraw(&raw_termios);
103     if (read_with_timeout) {
104         raw_termios.c_cc[VMIN] = 0; raw_termios.c_cc[VTIME] = 1;
105     } else {
106         raw_termios.c_cc[VMIN] = 1; raw_termios.c_cc[VTIME] = 0;
107     }
108     if (tcsetattr(fd, optional_actions, &raw_termios) != 0) { PyErr_SetFromErrno(PyExc_OSError); return false; }
109     return true;
110 }
111 
112 static PyObject*
open_tty(PyObject * self UNUSED,PyObject * args)113 open_tty(PyObject *self UNUSED, PyObject *args) {
114     int read_with_timeout = 0, optional_actions = TCSAFLUSH;
115     if (!PyArg_ParseTuple(args, "|pi", &read_with_timeout, &optional_actions)) return NULL;
116     int flags = O_RDWR | O_CLOEXEC | O_NOCTTY;
117     if (!read_with_timeout) flags |= O_NONBLOCK;
118     static char ctty[L_ctermid+1];
119     int fd = safe_open(ctermid(ctty), flags, 0);
120     if (fd == -1) { PyErr_Format(PyExc_OSError, "Failed to open controlling terminal: %s (identified with ctermid()) with error: %s", ctty, strerror(errno)); return NULL; }
121     struct termios *termios_p = calloc(1, sizeof(struct termios));
122     if (!termios_p) return PyErr_NoMemory();
123     if (tcgetattr(fd, termios_p) != 0) { free(termios_p); PyErr_SetFromErrno(PyExc_OSError); return NULL; }
124     if (!put_tty_in_raw_mode(fd, termios_p, read_with_timeout != 0, optional_actions)) { free(termios_p); return NULL; }
125     return Py_BuildValue("iN", fd, PyLong_FromVoidPtr(termios_p));
126 }
127 
128 #define TTY_ARGS \
129     PyObject *tp; int fd; int optional_actions = TCSAFLUSH; \
130     if (!PyArg_ParseTuple(args, "iO!|i", &fd, &PyLong_Type, &tp, &optional_actions)) return NULL; \
131     struct termios *termios_p = PyLong_AsVoidPtr(tp);
132 
133 static PyObject*
normal_tty(PyObject * self UNUSED,PyObject * args)134 normal_tty(PyObject *self UNUSED, PyObject *args) {
135     TTY_ARGS
136     if (tcsetattr(fd, optional_actions, termios_p) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
137     Py_RETURN_NONE;
138 }
139 
140 static PyObject*
raw_tty(PyObject * self UNUSED,PyObject * args)141 raw_tty(PyObject *self UNUSED, PyObject *args) {
142     TTY_ARGS
143     if (!put_tty_in_raw_mode(fd, termios_p, false, optional_actions)) return NULL;
144     Py_RETURN_NONE;
145 }
146 
147 
148 static PyObject*
close_tty(PyObject * self UNUSED,PyObject * args)149 close_tty(PyObject *self UNUSED, PyObject *args) {
150     TTY_ARGS
151     tcsetattr(fd, optional_actions, termios_p);  // deliberately ignore failure
152     free(termios_p);
153     safe_close(fd, __FILE__, __LINE__);
154     Py_RETURN_NONE;
155 }
156 
157 #undef TTY_ARGS
158 
159 static PyObject*
wcwidth_wrap(PyObject UNUSED * self,PyObject * chr)160 wcwidth_wrap(PyObject UNUSED *self, PyObject *chr) {
161     return PyLong_FromLong(wcwidth_std(PyLong_AsLong(chr)));
162 }
163 
164 static PyObject*
locale_is_valid(PyObject * self UNUSED,PyObject * args)165 locale_is_valid(PyObject *self UNUSED, PyObject *args) {
166     char *name;
167     if (!PyArg_ParseTuple(args, "s", &name)) return NULL;
168     locale_t test_locale = newlocale(LC_ALL_MASK, name, NULL);
169     if (!test_locale) { Py_RETURN_FALSE; }
170     freelocale(test_locale);
171     Py_RETURN_TRUE;
172 }
173 
174 static PyMethodDef module_methods[] = {
175     {"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
176     {"wcswidth", (PyCFunction)wcswidth_std, METH_O, ""},
177     {"open_tty", open_tty, METH_VARARGS, ""},
178     {"normal_tty", normal_tty, METH_VARARGS, ""},
179     {"raw_tty", raw_tty, METH_VARARGS, ""},
180     {"close_tty", close_tty, METH_VARARGS, ""},
181     {"set_iutf8_fd", (PyCFunction)pyset_iutf8, METH_VARARGS, ""},
182     {"thread_write", (PyCFunction)cm_thread_write, METH_VARARGS, ""},
183     {"parse_bytes", (PyCFunction)parse_bytes, METH_VARARGS, ""},
184     {"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""},
185     {"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""},
186     {"locale_is_valid", (PyCFunction)locale_is_valid, METH_VARARGS, ""},
187 #ifdef __APPLE__
188     METHODB(user_cache_dir, METH_NOARGS),
189     METHODB(process_group_map, METH_NOARGS),
190 #endif
191 #ifdef WITH_PROFILER
192     {"start_profiler", (PyCFunction)start_profiler, METH_VARARGS, ""},
193     {"stop_profiler", (PyCFunction)stop_profiler, METH_NOARGS, ""},
194 #endif
195     {NULL, NULL, 0, NULL}        /* Sentinel */
196 };
197 
198 
199 static struct PyModuleDef module = {
200    .m_base = PyModuleDef_HEAD_INIT,
201    .m_name = "fast_data_types",   /* name of module */
202    .m_doc = NULL,
203    .m_size = -1,
204    .m_methods = module_methods
205 };
206 
207 
208 extern int init_LineBuf(PyObject *);
209 extern int init_HistoryBuf(PyObject *);
210 extern int init_Cursor(PyObject *);
211 extern int init_DiskCache(PyObject *);
212 extern bool init_child_monitor(PyObject *);
213 extern int init_Line(PyObject *);
214 extern int init_ColorProfile(PyObject *);
215 extern int init_Screen(PyObject *);
216 extern bool init_fontconfig_library(PyObject*);
217 extern bool init_desktop(PyObject*);
218 extern bool init_fonts(PyObject*);
219 extern bool init_glfw(PyObject *m);
220 extern bool init_child(PyObject *m);
221 extern bool init_state(PyObject *module);
222 extern bool init_keys(PyObject *module);
223 extern bool init_graphics(PyObject *module);
224 extern bool init_shaders(PyObject *module);
225 extern bool init_mouse(PyObject *module);
226 extern bool init_kittens(PyObject *module);
227 extern bool init_logging(PyObject *module);
228 extern bool init_png_reader(PyObject *module);
229 #ifdef __APPLE__
230 extern int init_CoreText(PyObject *);
231 extern bool init_cocoa(PyObject *module);
232 extern bool init_macos_process_info(PyObject *module);
233 #else
234 extern bool init_freetype_library(PyObject*);
235 extern bool init_freetype_render_ui_text(PyObject*);
236 #endif
237 
238 
239 EXPORTED PyMODINIT_FUNC
PyInit_fast_data_types(void)240 PyInit_fast_data_types(void) {
241     PyObject *m;
242 
243     m = PyModule_Create(&module);
244     if (m == NULL) return NULL;
245     if (Py_AtExit(run_at_exit_cleanup_functions) != 0) {
246         PyErr_SetString(PyExc_RuntimeError, "Failed to register the atexit cleanup handler");
247         return NULL;
248     }
249     init_monotonic();
250 
251     if (!init_logging(m)) return NULL;
252     if (!init_LineBuf(m)) return NULL;
253     if (!init_HistoryBuf(m)) return NULL;
254     if (!init_Line(m)) return NULL;
255     if (!init_Cursor(m)) return NULL;
256     if (!init_DiskCache(m)) return NULL;
257     if (!init_child_monitor(m)) return NULL;
258     if (!init_ColorProfile(m)) return NULL;
259     if (!init_Screen(m)) return NULL;
260     if (!init_glfw(m)) return NULL;
261     if (!init_child(m)) return NULL;
262     if (!init_state(m)) return NULL;
263     if (!init_keys(m)) return NULL;
264     if (!init_graphics(m)) return NULL;
265     if (!init_shaders(m)) return NULL;
266     if (!init_mouse(m)) return NULL;
267     if (!init_kittens(m)) return NULL;
268     if (!init_png_reader(m)) return NULL;
269 #ifdef __APPLE__
270     if (!init_macos_process_info(m)) return NULL;
271     if (!init_CoreText(m)) return NULL;
272     if (!init_cocoa(m)) return NULL;
273 #else
274     if (!init_freetype_library(m)) return NULL;
275     if (!init_fontconfig_library(m)) return NULL;
276     if (!init_desktop(m)) return NULL;
277     if (!init_freetype_render_ui_text(m)) return NULL;
278 #endif
279     if (!init_fonts(m)) return NULL;
280 
281     PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT);
282     PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT);
283     PyModule_AddIntConstant(m, "REVERSE", REVERSE_SHIFT);
284     PyModule_AddIntConstant(m, "STRIKETHROUGH", STRIKE_SHIFT);
285     PyModule_AddIntConstant(m, "DIM", DIM_SHIFT);
286     PyModule_AddIntConstant(m, "DECORATION", DECORATION_SHIFT);
287     PyModule_AddIntConstant(m, "MARK", MARK_SHIFT);
288     PyModule_AddIntConstant(m, "MARK_MASK", MARK_MASK);
289     PyModule_AddStringMacro(m, ERROR_PREFIX);
290 #ifdef KITTY_VCS_REV
291     PyModule_AddStringMacro(m, KITTY_VCS_REV);
292 #endif
293     PyModule_AddIntMacro(m, CURSOR_BLOCK);
294     PyModule_AddIntMacro(m, CURSOR_BEAM);
295     PyModule_AddIntMacro(m, CURSOR_UNDERLINE);
296     PyModule_AddIntMacro(m, NO_CURSOR_SHAPE);
297     PyModule_AddIntMacro(m, DECAWM);
298     PyModule_AddIntMacro(m, DECCOLM);
299     PyModule_AddIntMacro(m, DECOM);
300     PyModule_AddIntMacro(m, IRM);
301     PyModule_AddIntMacro(m, CSI);
302     PyModule_AddIntMacro(m, DCS);
303     PyModule_AddIntMacro(m, APC);
304     PyModule_AddIntMacro(m, OSC);
305 
306     return m;
307 }
308