Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e7b957cc7 | ||
|
|
b1424cc4c7 | ||
|
|
be7d0e2016 | ||
|
|
7e8d509fdd | ||
|
|
8672a29503 | ||
|
|
32a1886b31 | ||
|
|
70bfe8589f | ||
|
|
4f3d71d4ee | ||
|
|
0df0fd6a0b | ||
|
|
2b9198866a | ||
|
|
6350652009 | ||
|
|
d239db8492 | ||
|
|
bdb6723564 | ||
|
|
d6bf5aa011 | ||
|
|
b5c4fbb42b | ||
|
|
1276bd6f5d |
@@ -3,6 +3,18 @@ Changelog
|
|||||||
|
|
||||||
kitty is a feature full, cross-platform, *fast*, OpenGL based terminal emulator.
|
kitty is a feature full, cross-platform, *fast*, OpenGL based terminal emulator.
|
||||||
|
|
||||||
|
|
||||||
|
version 0.4.2 [2017-10-23]
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
- Fix a regression in 0.4.0 that broke custom key mappings
|
||||||
|
|
||||||
|
- Fix a regression in 0.4.0 that broke support for non-QWERTY keyboard layouts
|
||||||
|
|
||||||
|
- Avoid using threads to reap zombie child processes. Also prevent kitty from
|
||||||
|
hanging if the open program hangs when clicking on a URL.
|
||||||
|
|
||||||
|
|
||||||
version 0.4.0 [2017-10-22]
|
version 0.4.0 [2017-10-22]
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -662,37 +662,11 @@ hangup(pid_t pid) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static pid_t pid_buf[MAX_CHILDREN] = {0};
|
|
||||||
static size_t pid_buf_pos = 0;
|
|
||||||
static pthread_t reap_thread;
|
|
||||||
|
|
||||||
|
|
||||||
static void*
|
|
||||||
reap(void *pid_p) {
|
|
||||||
set_thread_name("KittyReapChild");
|
|
||||||
pid_t pid = *((pid_t*)pid_p);
|
|
||||||
while(true) {
|
|
||||||
pid_t ret = waitpid(pid, NULL, 0);
|
|
||||||
if (ret != pid) {
|
|
||||||
if (errno == EINTR) continue;
|
|
||||||
fprintf(stderr, "Failed to reap child process with pid: %d with error: %s\n", pid, strerror(errno));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
cleanup_child(ssize_t i) {
|
cleanup_child(ssize_t i) {
|
||||||
close(children[i].fd);
|
close(children[i].fd);
|
||||||
hangup(children[i].pid);
|
hangup(children[i].pid);
|
||||||
pid_buf[pid_buf_pos] = children[i].pid;
|
|
||||||
if (waitpid(pid_buf[pid_buf_pos], NULL, WNOHANG) != pid_buf[pid_buf_pos]) {
|
|
||||||
errno = 0;
|
|
||||||
int ret = pthread_create(&reap_thread, NULL, reap, pid_buf + pid_buf_pos);
|
|
||||||
if (ret != 0) perror("Failed to create thread to reap child");
|
|
||||||
}
|
|
||||||
pid_buf_pos = (pid_buf_pos + 1) % MAX_CHILDREN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -324,16 +324,18 @@ with open(
|
|||||||
defaults = parse_config(f.readlines(), check_keys=False)
|
defaults = parse_config(f.readlines(), check_keys=False)
|
||||||
Options = namedtuple('Defaults', ','.join(defaults.keys()))
|
Options = namedtuple('Defaults', ','.join(defaults.keys()))
|
||||||
defaults = Options(**defaults)
|
defaults = Options(**defaults)
|
||||||
actions = frozenset(a.func for a in defaults.keymap.values())
|
actions = frozenset(a.func for a in defaults.keymap.values()) | frozenset({'combine'})
|
||||||
|
no_op_actions = frozenset({'noop', 'no-op', 'no_op'})
|
||||||
|
|
||||||
|
|
||||||
def merge_keymaps(defaults, newvals):
|
def merge_keymaps(defaults, newvals):
|
||||||
ans = defaults.copy()
|
ans = defaults.copy()
|
||||||
for k, v in newvals.items():
|
for k, v in newvals.items():
|
||||||
if v in {'noop', 'no-op', 'no_op'}:
|
f = v.func
|
||||||
|
if f in no_op_actions:
|
||||||
ans.pop(k, None)
|
ans.pop(k, None)
|
||||||
continue
|
continue
|
||||||
if v in actions:
|
if f in actions:
|
||||||
ans[k] = v
|
ans[k] = v
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from collections import namedtuple
|
|||||||
from .fast_data_types import set_boss as set_c_boss
|
from .fast_data_types import set_boss as set_c_boss
|
||||||
|
|
||||||
appname = 'kitty'
|
appname = 'kitty'
|
||||||
version = (0, 4, 0)
|
version = (0, 4, 2)
|
||||||
str_version = '.'.join(map(str, version))
|
str_version = '.'.join(map(str, version))
|
||||||
_plat = sys.platform.lower()
|
_plat = sys.platform.lower()
|
||||||
isosx = 'darwin' in _plat
|
isosx = 'darwin' in _plat
|
||||||
@@ -124,4 +124,12 @@ def x11_window_id(window):
|
|||||||
return lib.glfwGetX11Window(window.window_id())
|
return lib.glfwGetX11Window(window.window_id())
|
||||||
|
|
||||||
|
|
||||||
|
def x11_display():
|
||||||
|
lib = glfw_lib()
|
||||||
|
ans = lib.glfwGetX11Display
|
||||||
|
ans.restype = ctypes.c_void_p
|
||||||
|
ans.argtypes = []
|
||||||
|
return ans()
|
||||||
|
|
||||||
|
|
||||||
iswayland = not isosx and hasattr(glfw_lib(), 'glfwGetWaylandDisplay')
|
iswayland = not isosx and hasattr(glfw_lib(), 'glfwGetWaylandDisplay')
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#include "modes.h"
|
#include "modes.h"
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
#ifdef WITH_PROFILER
|
#ifdef WITH_PROFILER
|
||||||
#include <gperftools/profiler.h>
|
#include <gperftools/profiler.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -81,6 +83,28 @@ pyset_iutf8(PyObject UNUSED *self, PyObject *args) {
|
|||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
handle_sigchld(int UNUSED signum, siginfo_t *sinfo, void UNUSED *unused) {
|
||||||
|
if (sinfo->si_code != CLD_EXITED) return;
|
||||||
|
int sav_errno = errno, status;
|
||||||
|
while(true) {
|
||||||
|
if (waitpid(sinfo->si_pid, &status, WNOHANG) == -1) {
|
||||||
|
if (errno != EINTR) break;
|
||||||
|
} else break;
|
||||||
|
}
|
||||||
|
errno = sav_errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
install_sigchld_handler(PyObject UNUSED *self) {
|
||||||
|
struct sigaction sa;
|
||||||
|
sa.sa_flags = SA_SIGINFO;
|
||||||
|
sa.sa_sigaction = handle_sigchld;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
if (sigaction(SIGCHLD, &sa, NULL) == -1) return PyErr_SetFromErrno(PyExc_OSError);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef WITH_PROFILER
|
#ifdef WITH_PROFILER
|
||||||
static PyObject*
|
static PyObject*
|
||||||
start_profiler(PyObject UNUSED *self, PyObject *args) {
|
start_profiler(PyObject UNUSED *self, PyObject *args) {
|
||||||
@@ -105,6 +129,7 @@ static PyMethodDef module_methods[] = {
|
|||||||
{"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""},
|
{"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""},
|
||||||
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
|
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
|
||||||
{"change_wcwidth", (PyCFunction)change_wcwidth_wrap, METH_O, ""},
|
{"change_wcwidth", (PyCFunction)change_wcwidth_wrap, METH_O, ""},
|
||||||
|
{"install_sigchld_handler", (PyCFunction)install_sigchld_handler, METH_NOARGS, ""},
|
||||||
#ifdef WITH_PROFILER
|
#ifdef WITH_PROFILER
|
||||||
{"start_profiler", (PyCFunction)start_profiler, METH_VARARGS, ""},
|
{"start_profiler", (PyCFunction)start_profiler, METH_VARARGS, ""},
|
||||||
{"stop_profiler", (PyCFunction)stop_profiler, METH_NOARGS, ""},
|
{"stop_profiler", (PyCFunction)stop_profiler, METH_NOARGS, ""},
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
from kitty.fast_data_types import Face, get_fontconfig_font
|
from kitty.fast_data_types import Face, get_fontconfig_font
|
||||||
@@ -35,51 +34,7 @@ def font_not_found(err, char):
|
|||||||
return FontNotFound(msg)
|
return FontNotFound(msg)
|
||||||
|
|
||||||
|
|
||||||
def get_font_subprocess(
|
def get_font(
|
||||||
family='monospace',
|
|
||||||
bold=False,
|
|
||||||
italic=False,
|
|
||||||
allow_bitmaped_fonts=False,
|
|
||||||
size_in_pts=None,
|
|
||||||
character=None,
|
|
||||||
dpi=None
|
|
||||||
):
|
|
||||||
query = escape_family_name(family)
|
|
||||||
if character is not None:
|
|
||||||
query += ':charset={:x}'.format(ord(character[0]))
|
|
||||||
if not allow_bitmaped_fonts:
|
|
||||||
query += ':scalable=true:outline=true'
|
|
||||||
if size_in_pts is not None:
|
|
||||||
query += ':size={:.1f}'.format(size_in_pts)
|
|
||||||
if dpi is not None:
|
|
||||||
query += ':dpi={:.1f}'.format(dpi)
|
|
||||||
if bold:
|
|
||||||
query += ':weight=200'
|
|
||||||
if italic:
|
|
||||||
query += ':slant=100'
|
|
||||||
try:
|
|
||||||
raw = subprocess.check_output([
|
|
||||||
'fc-match', query, '-f',
|
|
||||||
'%{file}\x1e%{hinting}\x1e%{hintstyle}\x1e%{scalable}\x1e%{outline}\x1e%{weight}\x1e%{slant}\x1e%{index}'
|
|
||||||
]).decode('utf-8')
|
|
||||||
except subprocess.CalledProcessError as err:
|
|
||||||
raise font_not_found(err, character)
|
|
||||||
parts = raw.split('\x1e')
|
|
||||||
try:
|
|
||||||
path, hinting, hintstyle, scalable, outline, weight, slant, index = parts
|
|
||||||
except ValueError:
|
|
||||||
path = parts[0]
|
|
||||||
hintstyle, hinting, scalable, outline, weight, slant, index = 1, 'True', 'True', 'True', 100, 0, 0
|
|
||||||
return Font(
|
|
||||||
path,
|
|
||||||
to_bool(hinting),
|
|
||||||
int(hintstyle), bold, italic,
|
|
||||||
to_bool(scalable),
|
|
||||||
to_bool(outline), int(weight), int(slant), int(index)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_font_lib(
|
|
||||||
family='monospace',
|
family='monospace',
|
||||||
bold=False,
|
bold=False,
|
||||||
italic=False,
|
italic=False,
|
||||||
@@ -102,9 +57,6 @@ def get_font_lib(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
get_font = get_font_lib
|
|
||||||
|
|
||||||
|
|
||||||
def find_font_for_character(
|
def find_font_for_character(
|
||||||
family,
|
family,
|
||||||
char,
|
char,
|
||||||
|
|||||||
51
kitty/keys.c
51
kitty/keys.c
@@ -89,31 +89,32 @@ get_localized_key(int key, int scancode) {
|
|||||||
switch(name[0]) {
|
switch(name[0]) {
|
||||||
#define K(ch, name) case ch: return GLFW_KEY_##name
|
#define K(ch, name) case ch: return GLFW_KEY_##name
|
||||||
// key names {{{
|
// key names {{{
|
||||||
K('A', A);
|
K('A', A); K('a', A);
|
||||||
K('B', B);
|
K('B', B); K('b', B);
|
||||||
K('C', C);
|
K('C', C); K('c', C);
|
||||||
K('D', D);
|
K('D', D); K('d', D);
|
||||||
K('E', E);
|
K('E', E); K('e', E);
|
||||||
K('F', F);
|
K('F', F); K('f', F);
|
||||||
K('G', G);
|
K('G', G); K('g', G);
|
||||||
K('H', H);
|
K('H', H); K('h', H);
|
||||||
K('I', I);
|
K('I', I); K('i', I);
|
||||||
K('J', J);
|
K('J', J); K('j', J);
|
||||||
K('K', K);
|
K('K', K); K('k', K);
|
||||||
K('L', L);
|
K('L', L); K('l', L);
|
||||||
K('M', M);
|
K('M', M); K('m', M);
|
||||||
K('N', N);
|
K('N', N); K('n', N);
|
||||||
K('O', O);
|
K('O', O); K('o', O);
|
||||||
K('P', P);
|
K('P', P); K('p', P);
|
||||||
K('Q', Q);
|
K('Q', Q); K('q', Q);
|
||||||
K('S', S);
|
K('R', R); K('r', R);
|
||||||
K('T', T);
|
K('S', S); K('s', S);
|
||||||
K('U', U);
|
K('T', T); K('t', T);
|
||||||
K('V', V);
|
K('U', U); K('u', U);
|
||||||
K('W', W);
|
K('V', V); K('v', V);
|
||||||
K('X', X);
|
K('W', W); K('w', W);
|
||||||
K('Y', Y);
|
K('X', X); K('x', X);
|
||||||
K('Z', Z);
|
K('Y', Y); K('y', Y);
|
||||||
|
K('Z', Z); K('z', Z);
|
||||||
K('0', 0);
|
K('0', 0);
|
||||||
K('1', 1);
|
K('1', 1);
|
||||||
K('2', 2);
|
K('2', 2);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import locale
|
import locale
|
||||||
import os
|
import os
|
||||||
|
import signal
|
||||||
import sys
|
import sys
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
@@ -23,8 +24,8 @@ from .fast_data_types import (
|
|||||||
GLFW_OPENGL_FORWARD_COMPAT, GLFW_OPENGL_PROFILE, GLFW_SAMPLES,
|
GLFW_OPENGL_FORWARD_COMPAT, GLFW_OPENGL_PROFILE, GLFW_SAMPLES,
|
||||||
GLFW_STENCIL_BITS, GLFWWindow, change_wcwidth, check_for_extensions,
|
GLFW_STENCIL_BITS, GLFWWindow, change_wcwidth, check_for_extensions,
|
||||||
clear_buffers, glewInit, glfw_init, glfw_init_hint_string,
|
clear_buffers, glewInit, glfw_init, glfw_init_hint_string,
|
||||||
glfw_swap_interval, glfw_terminate, glfw_window_hint, set_logical_dpi,
|
glfw_swap_interval, glfw_terminate, glfw_window_hint,
|
||||||
set_options
|
install_sigchld_handler, set_logical_dpi, set_options
|
||||||
)
|
)
|
||||||
from .layout import all_layouts
|
from .layout import all_layouts
|
||||||
from .utils import color_as_int, detach, get_logical_dpi, safe_print
|
from .utils import color_as_int, detach, get_logical_dpi, safe_print
|
||||||
@@ -235,11 +236,11 @@ def setup_profiling(args):
|
|||||||
exe = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'kitty-profile')
|
exe = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'kitty-profile')
|
||||||
cg = '/tmp/kitty-profile.callgrind'
|
cg = '/tmp/kitty-profile.callgrind'
|
||||||
print('Post processing profile data for', exe, '...')
|
print('Post processing profile data for', exe, '...')
|
||||||
subprocess.check_call(['pprof', '--callgrind', exe, '/tmp/kitty-profile.log'], stdout=open(cg, 'wb'))
|
subprocess.call(['pprof', '--callgrind', exe, '/tmp/kitty-profile.log'], stdout=open(cg, 'wb'))
|
||||||
try:
|
try:
|
||||||
subprocess.Popen(['kcachegrind', cg])
|
subprocess.Popen(['kcachegrind', cg])
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
subprocess.check_call(['pprof', '--text', exe, '/tmp/kitty-profile.log'])
|
subprocess.call(['pprof', '--text', exe, '/tmp/kitty-profile.log'])
|
||||||
print('To view the graphical call data, use: kcachegrind', cg)
|
print('To view the graphical call data, use: kcachegrind', cg)
|
||||||
|
|
||||||
|
|
||||||
@@ -286,6 +287,9 @@ def main():
|
|||||||
raise SystemExit('GLFW initialization failed')
|
raise SystemExit('GLFW initialization failed')
|
||||||
try:
|
try:
|
||||||
with setup_profiling(args):
|
with setup_profiling(args):
|
||||||
|
# Avoid needing to launch threads to reap zombies
|
||||||
|
install_sigchld_handler()
|
||||||
run_app(opts, args)
|
run_app(opts, args)
|
||||||
|
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
|
||||||
finally:
|
finally:
|
||||||
glfw_terminate()
|
glfw_terminate()
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from contextlib import contextmanager
|
|||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from time import monotonic
|
from time import monotonic
|
||||||
|
|
||||||
from .constants import isosx, iswayland, selection_clipboard_funcs
|
from .constants import isosx, iswayland, selection_clipboard_funcs, x11_display
|
||||||
from .fast_data_types import (
|
from .fast_data_types import (
|
||||||
GLSL_VERSION, glfw_get_physical_dpi, redirect_std_streams,
|
GLSL_VERSION, glfw_get_physical_dpi, redirect_std_streams,
|
||||||
wcwidth as wcwidth_impl
|
wcwidth as wcwidth_impl
|
||||||
@@ -72,7 +72,6 @@ def load_libx11():
|
|||||||
import ctypes
|
import ctypes
|
||||||
from ctypes.util import find_library
|
from ctypes.util import find_library
|
||||||
libx11 = ctypes.CDLL(find_library('X11'))
|
libx11 = ctypes.CDLL(find_library('X11'))
|
||||||
ans = []
|
|
||||||
|
|
||||||
def cdef(name, restype, *argtypes):
|
def cdef(name, restype, *argtypes):
|
||||||
f = getattr(libx11, name)
|
f = getattr(libx11, name)
|
||||||
@@ -80,43 +79,27 @@ def load_libx11():
|
|||||||
f.restype = restype
|
f.restype = restype
|
||||||
if argtypes:
|
if argtypes:
|
||||||
f.argtypes = argtypes
|
f.argtypes = argtypes
|
||||||
ans.append(f)
|
return f
|
||||||
|
|
||||||
cdef('XOpenDisplay', ctypes.c_void_p, ctypes.c_char_p)
|
return cdef('XResourceManagerString', ctypes.c_char_p, ctypes.c_void_p)
|
||||||
cdef('XCloseDisplay', ctypes.c_int, ctypes.c_void_p)
|
|
||||||
cdef('XResourceManagerString', ctypes.c_char_p, ctypes.c_void_p)
|
|
||||||
return ans
|
|
||||||
|
|
||||||
|
|
||||||
def parse_xrdb(raw):
|
def parse_xrdb(raw):
|
||||||
q = 'Xft.dpi:\t'
|
q = 'Xft.dpi:\t'
|
||||||
for line in raw.decode('utf-8').splitlines():
|
for line in raw.decode('utf-8', 'replace').splitlines():
|
||||||
if line.startswith(q):
|
if line.startswith(q):
|
||||||
return float(line[len(q):])
|
return float(line[len(q):])
|
||||||
|
|
||||||
|
|
||||||
def x11_dpi_native():
|
|
||||||
XOpenDisplay, XCloseDisplay, XResourceManagerString = load_libx11()
|
|
||||||
display = XOpenDisplay(None)
|
|
||||||
if display is None:
|
|
||||||
raise RuntimeError('Could not connect to the X server')
|
|
||||||
try:
|
|
||||||
raw = XResourceManagerString(display)
|
|
||||||
return parse_xrdb(raw)
|
|
||||||
finally:
|
|
||||||
XCloseDisplay(display)
|
|
||||||
|
|
||||||
|
|
||||||
def x11_dpi():
|
def x11_dpi():
|
||||||
try:
|
XResourceManagerString = load_libx11()
|
||||||
return x11_dpi_native()
|
display = x11_display()
|
||||||
except Exception:
|
if display:
|
||||||
pass
|
try:
|
||||||
try:
|
raw = XResourceManagerString(display)
|
||||||
raw = subprocess.check_output(['xrdb', '-query'], stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)
|
return parse_xrdb(raw)
|
||||||
return parse_xrdb(raw)
|
except Exception:
|
||||||
except Exception:
|
pass
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def get_logical_dpi():
|
def get_logical_dpi():
|
||||||
@@ -182,7 +165,8 @@ def get_primary_selection():
|
|||||||
return '' # There is no primary selection on OS X
|
return '' # There is no primary selection on OS X
|
||||||
g = selection_clipboard_funcs()[0]
|
g = selection_clipboard_funcs()[0]
|
||||||
if g is None:
|
if g is None:
|
||||||
ans = subprocess.check_output(['xsel', '-p'], stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL).decode('utf-8')
|
# We cannot use check_output as we set a SIGCHLD handler to reap zombies
|
||||||
|
ans = subprocess.Popen(['xsel', '-p'], stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE).stdout.read().decode('utf-8')
|
||||||
if ans:
|
if ans:
|
||||||
# Without this for some reason repeated pastes dont work
|
# Without this for some reason repeated pastes dont work
|
||||||
set_primary_selection(ans)
|
set_primary_selection(ans)
|
||||||
@@ -209,7 +193,7 @@ def open_cmd(cmd, arg=None):
|
|||||||
if arg is not None:
|
if arg is not None:
|
||||||
cmd = list(cmd)
|
cmd = list(cmd)
|
||||||
cmd.append(arg)
|
cmd.append(arg)
|
||||||
return subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).wait()
|
return subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
|
||||||
def open_url(url, program='default'):
|
def open_url(url, program='default'):
|
||||||
|
|||||||
Reference in New Issue
Block a user