2615 lines
76 KiB
Python
Executable File
2615 lines
76 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import enum
|
|
import datetime
|
|
import functools
|
|
import io
|
|
import json
|
|
import logging
|
|
import optparse
|
|
import os
|
|
import pprint
|
|
import re
|
|
import select
|
|
import signal
|
|
import socket
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
import traceback
|
|
|
|
|
|
def custom_notify(
|
|
title=None,
|
|
msg=None,
|
|
timeout=None,
|
|
):
|
|
if timeout is None:
|
|
timeout = 5
|
|
|
|
timeout2 = int(timeout * 1000)
|
|
|
|
assert isinstance(timeout2, int) and timeout2 >= 500
|
|
|
|
if title is None:
|
|
title = 'commands'
|
|
|
|
assert isinstance(title, str) and len(title) > 0
|
|
assert isinstance(msg, str) and len(msg) > 0
|
|
|
|
if sys.platform == 'darwin':
|
|
osascript_translate = functools.partial(
|
|
custom_translate,
|
|
check=lambda a, b:
|
|
not re.compile(
|
|
r'^[a-zA-Z0-9\<\>\/\(\)\s\.\,\:]*$'
|
|
)\
|
|
.match(b) is None,
|
|
)
|
|
|
|
subprocess.check_call([
|
|
'osascript',
|
|
'-e',
|
|
'display notification "%s" with title "%s"' % (
|
|
osascript_translate(msg),
|
|
osascript_translate(title),
|
|
)
|
|
])
|
|
else:
|
|
subprocess.check_call([
|
|
'notify-send',
|
|
'-t', '%d' % timeout2,
|
|
title,
|
|
msg[-128:]
|
|
])
|
|
|
|
def intercept_output(
|
|
current_subprocess,
|
|
return_aggregated=None,
|
|
transform_callback=None,
|
|
real_time=None,
|
|
timeout=None,
|
|
):
|
|
if real_time is None:
|
|
real_time = False
|
|
|
|
start_timestamp = datetime.datetime.now()
|
|
|
|
if not return_aggregated:
|
|
return_aggregated = False
|
|
|
|
t1 = select.poll()
|
|
t1.register(current_subprocess.stdout, select.POLLIN)
|
|
print([current_subprocess, current_subprocess.poll()])
|
|
output = []
|
|
|
|
last_data = None
|
|
|
|
while not (
|
|
not current_subprocess.poll() is None and \
|
|
not last_data is None
|
|
):
|
|
if not timeout is None and \
|
|
(datetime.datetime.now() - start_timestamp).total_seconds() > timeout:
|
|
break
|
|
|
|
t2 = t1.poll(100)
|
|
if len(t2) == 1 and (t2[0][1] & select.POLLIN) > 0 and \
|
|
not (isinstance(last_data, bytes) and len(last_data) == 0):
|
|
t3 = current_subprocess.stdout.peek()
|
|
t4 = current_subprocess.stdout.read(len(t3))
|
|
last_data = t3
|
|
output.append(t3)
|
|
yield dict(
|
|
data=t3,
|
|
aggregated=False,
|
|
)
|
|
t6 = t3
|
|
if not transform_callback is None:
|
|
t5 = transform_callback(t3)
|
|
if not t5 is None:
|
|
t6 = t5
|
|
os.write(sys.stdout.fileno(), t6)
|
|
elif real_time:
|
|
yield dict(
|
|
data=b'',
|
|
aggregated=False,
|
|
)
|
|
|
|
if return_aggregated:
|
|
yield dict(
|
|
data=b''.join(output),
|
|
aggregated=True,
|
|
returncode=current_subprocess.poll(),
|
|
)
|
|
|
|
def player_metadata():
|
|
for k in range(20):
|
|
try:
|
|
time.sleep(1.0)
|
|
return subprocess.check_output(['playerctl', 'metadata']).decode('utf-8').strip()
|
|
except:
|
|
continue
|
|
|
|
def memory_stats():
|
|
if sys.platform == 'linux':
|
|
with io.BytesIO(
|
|
subprocess.check_output(
|
|
'free',
|
|
shell=True
|
|
)
|
|
) as f:
|
|
t1 = f.read().decode('utf-8').splitlines()
|
|
mem_total = int(t1[1].strip().split()[1])
|
|
mem_used = \
|
|
int(t1[1].strip().split()[2]) + \
|
|
int(t1[1].strip().split()[4])
|
|
|
|
return dict(
|
|
mem_total=mem_total,
|
|
mem_used=mem_used,
|
|
)
|
|
elif sys.platform == 'darwin':
|
|
sysctl_value = lambda name, custom_cast=int: \
|
|
custom_cast(
|
|
subprocess.check_output(
|
|
'sysctl -a | grep %s' % name,
|
|
shell=True,
|
|
).decode('utf-8').split(':')[1]
|
|
)
|
|
|
|
vm_pagesize = sysctl_value('vm.pagesize')
|
|
mem_total = sysctl_value('hw.memsize')
|
|
|
|
t1 = subprocess.check_output('vm_stat').decode('utf-8')
|
|
t2 = [o.split(':') for o in t1.splitlines() if ':' in o]
|
|
t3 = {
|
|
o[0].replace(' ', '_').replace('-', '_').lower() \
|
|
: \
|
|
int(o[1].strip().rstrip('.'))
|
|
for o in t2
|
|
if len(o) == 2 and len(o[0]) > 0 \
|
|
and not re.compile(r'^\s*\d+\.\s*$').match(o[1]) is None \
|
|
and not re.compile(r'^[a-zA-Z0-9\_\-\s]+$').match(o[0]) is None
|
|
}
|
|
mem_used = (
|
|
t3['pages_active'] + \
|
|
t3['pages_wired_down']
|
|
) * vm_pagesize
|
|
|
|
return dict(
|
|
mem_total=mem_total / 1024,
|
|
mem_used=mem_used / 1024,
|
|
)
|
|
else:
|
|
raise NotImplementedError
|
|
|
|
def chrome(
|
|
argv
|
|
):
|
|
assert isinstance(argv, list) and all([isinstance(o, str) for o in argv])
|
|
parser = optparse.OptionParser()
|
|
parser.add_option(
|
|
'--user_data_dir',
|
|
dest='user_data_dir',
|
|
default=None,
|
|
type=str,
|
|
)
|
|
|
|
options, args = parser.parse_args(argv)
|
|
|
|
if options.user_data_dir is None:
|
|
options.user_data_dir = os.path.join(
|
|
os.environ['HOME'],
|
|
'.config',
|
|
'google-chrome',
|
|
)
|
|
|
|
#assert os.path.exists(options.user_data_dir)
|
|
|
|
if sys.platform == 'linux':
|
|
subprocess.check_call([
|
|
'google-chrome-stable',
|
|
'--enable-features=useOzonePlatform',
|
|
'--ozone-platform=wayland',
|
|
'--process-per-site',
|
|
'--user-data-dir=%s' % options.user_data_dir,
|
|
*args,
|
|
])
|
|
else:
|
|
raise NotImplementedError
|
|
|
|
def eternal_oom(argv):
|
|
import signal
|
|
import re
|
|
import time
|
|
import pprint
|
|
|
|
assert isinstance(argv, list) and all([isinstance(o, str) for o in argv])
|
|
parser = optparse.OptionParser()
|
|
parser.add_option(
|
|
'--cpu_wait',
|
|
dest='cpu_wait',
|
|
default=None,
|
|
type=float,
|
|
)
|
|
parser.add_option(
|
|
'--mean_size',
|
|
dest='mean_size',
|
|
default=None,
|
|
type=int,
|
|
)
|
|
parser.add_option(
|
|
'--one_shot_clean',
|
|
dest='one_shot_clean',
|
|
action='store_true',
|
|
default=None,
|
|
)
|
|
parser.add_option(
|
|
'--one_shot_app',
|
|
dest='one_shot_app',
|
|
default=[],
|
|
action='append',
|
|
)
|
|
parser.add_option(
|
|
'--period',
|
|
dest='period',
|
|
default=None,
|
|
type=float,
|
|
)
|
|
parser.add_option(
|
|
'--memory_limit',
|
|
dest='memory_limit',
|
|
default=None,
|
|
type=float,
|
|
)
|
|
parser.add_option(
|
|
'--cpu_limit',
|
|
dest='cpu_limit',
|
|
default=None,
|
|
type=float,
|
|
)
|
|
parser.add_option(
|
|
'--debug',
|
|
dest='debug',
|
|
action='store_true',
|
|
default=False,
|
|
)
|
|
options, args = parser.parse_args(argv)
|
|
|
|
self_pid = os.getpid()
|
|
|
|
if isinstance(options.one_shot_clean, bool) and options.one_shot_clean:
|
|
if len(options.one_shot_app) == 0:
|
|
options.one_shot_app = ['chrome', 'teams']
|
|
|
|
config = dict(
|
|
chrome=(r'chrome.*type=renderer', r'^.*--extension-process.*$'),
|
|
teams=(r'teams.*type=renderer', None),
|
|
)
|
|
|
|
for app in options.one_shot_app:
|
|
p = config[app]
|
|
|
|
try:
|
|
t1 = subprocess.check_output([
|
|
'pgrep', '-a', '-f', p[0]
|
|
]).decode('utf-8')
|
|
except:
|
|
continue
|
|
t2 = t1.splitlines()
|
|
if not p[1] is None:
|
|
t3 = [o for o in t2 if re.compile(p[1]).match(o) is None]
|
|
else:
|
|
t3 = t2
|
|
t4 = [
|
|
int(o.split()[0]) for o in t3
|
|
]
|
|
|
|
for pid in t4:
|
|
if pid == self_pid:
|
|
raise NotImplementedError
|
|
|
|
os.kill(pid, signal.SIGTERM)
|
|
|
|
logging.info(json.dumps(dict(
|
|
apps=options.one_shot_app,
|
|
count=len(t4),
|
|
processes=[
|
|
o.split()[:3] for o in t3
|
|
],
|
|
)))
|
|
|
|
if len(t4) > 0:
|
|
print(
|
|
'\n'.join([
|
|
str(o.split()[:3]) for o in t3
|
|
])
|
|
)
|
|
return
|
|
|
|
if options.period is None:
|
|
options.period = 1
|
|
if options.memory_limit is None:
|
|
options.memory_limit = 3 * 1024 * 1024
|
|
if options.cpu_limit is None:
|
|
options.cpu_limit = 0.6 * os.cpu_count()
|
|
if options.cpu_wait is None:
|
|
options.cpu_wait = 10
|
|
if options.mean_size is None:
|
|
options.mean_size = 30
|
|
|
|
if isinstance(options.memory_limit, float):
|
|
options.memory_limit = int(options.memory_limit)
|
|
|
|
assert isinstance(options.memory_limit, int) \
|
|
and options.memory_limit < memory_stats()['mem_total'] * 0.95 \
|
|
and options.memory_limit > 512 * 1024
|
|
|
|
assert isinstance(options.cpu_limit, float) \
|
|
and options.cpu_limit > 0.2 * os.cpu_count() and \
|
|
options.cpu_limit < os.cpu_count() * 0.95
|
|
|
|
assert options.period >= 1
|
|
|
|
assert options.cpu_wait >= 10
|
|
assert options.mean_size >= 16
|
|
|
|
def pandas_data_frame(lines, groups_regex, header_regex, extra_columns):
|
|
header = re.compile(header_regex).search(lines[0]).groups()
|
|
rows = [
|
|
re.compile(groups_regex).search(row).groups()
|
|
for row in lines[1:]
|
|
]
|
|
columns = {
|
|
column: []
|
|
for column in header
|
|
}
|
|
for row in rows:
|
|
for value, column in zip(row, header):
|
|
columns[column].append(value)
|
|
for column, transformation in extra_columns.items():
|
|
columns[column] = [
|
|
transformation(
|
|
{
|
|
k : v[index]
|
|
for k, v in columns.items()
|
|
}
|
|
)
|
|
for index in range(len(rows))
|
|
]
|
|
|
|
return columns
|
|
|
|
def pandas_merge(left, right, on):
|
|
index = {}
|
|
input_data_frames = [
|
|
('left', left),
|
|
('right', right),
|
|
]
|
|
for index_name, data_frame in input_data_frames:
|
|
current_index = {}
|
|
for row_index, value in enumerate(data_frame[on]):
|
|
if not value in current_index:
|
|
current_index[value] = []
|
|
current_index[value].append(row_index)
|
|
|
|
index[index_name] = current_index
|
|
|
|
merged_data_frame = dict(
|
|
header=[
|
|
column + '_x'
|
|
for column in left
|
|
] + [
|
|
column + '_y'
|
|
for column in right
|
|
],
|
|
columns={},
|
|
)
|
|
|
|
for column in merged_data_frame['header']:
|
|
merged_data_frame['columns'][column] = []
|
|
|
|
common_values = {
|
|
left_value
|
|
for left_value in index['left']
|
|
if left_value in index['right']
|
|
}
|
|
common_rows = sorted(
|
|
[
|
|
dict(
|
|
left_row_index=index['left'][value][0],
|
|
right_row_index=index['right'][value][0],
|
|
)
|
|
for value in common_values
|
|
],
|
|
key=lambda x: x['left_row_index'],
|
|
)
|
|
for common_row in common_rows:
|
|
row = sum([
|
|
[
|
|
values[
|
|
common_row['%s_row_index' % index_name]
|
|
]
|
|
for column, values in data_frame.items()
|
|
]
|
|
for index_name, data_frame in input_data_frames
|
|
], [])
|
|
for column, value in zip(merged_data_frame['header'], row):
|
|
merged_data_frame['columns'][column].append(value)
|
|
|
|
return merged_data_frame['columns']
|
|
|
|
def pandas_sort_values(data_frame, by, ascending):
|
|
assert len(by) == 1
|
|
assert ascending is False
|
|
t1 = [
|
|
o['row_index']
|
|
for o in sorted(
|
|
[
|
|
dict(
|
|
row_index=row_index,
|
|
value=value
|
|
)
|
|
for row_index, value in enumerate(data_frame[by[0]])
|
|
],
|
|
key=lambda x: x['value']
|
|
)[::-1]
|
|
]
|
|
return {
|
|
column : [
|
|
values[row_index]
|
|
for row_index in t1
|
|
]
|
|
for column, values in data_frame.items()
|
|
}
|
|
|
|
def pandas_filter_values(data_frame, condition):
|
|
shape = [
|
|
len(data_frame),
|
|
]
|
|
if shape[0] > 0:
|
|
shape.append(
|
|
len(list(data_frame.values())[0])
|
|
)
|
|
t1 = [
|
|
row_index
|
|
for row_index in range(shape[1])
|
|
if condition(
|
|
{
|
|
column : values[row_index]
|
|
for column, values in data_frame.items()
|
|
}
|
|
)
|
|
]
|
|
return {
|
|
column : [
|
|
values[row_index]
|
|
for row_index in t1
|
|
]
|
|
for column, values in data_frame.items()
|
|
}
|
|
|
|
def pandas_row(data_frame, row_index):
|
|
return {
|
|
column : values[row_index]
|
|
for column, values in data_frame.items()
|
|
}
|
|
|
|
def pandas_shape(data_frame):
|
|
columns_count = len(data_frame)
|
|
if columns_count > 0:
|
|
rows_count = len(data_frame[
|
|
next(iter(data_frame.keys()))
|
|
])
|
|
else:
|
|
rows_count = 0
|
|
|
|
return [
|
|
columns_count,
|
|
rows_count,
|
|
]
|
|
|
|
def ps_regex(groups_cnt):
|
|
assert groups_cnt >= 1
|
|
return ''.join([
|
|
r'^\s*',
|
|
r'([^\s]+)\s+' * (groups_cnt - 1),
|
|
r'([^\s]+)\s*$',
|
|
])
|
|
|
|
def oom_get_processes():
|
|
with io.BytesIO(
|
|
subprocess.check_output(
|
|
'ps -e -o pid,rss,user,%cpu',
|
|
shell=True
|
|
)
|
|
) as f:
|
|
t1 = pandas_data_frame(
|
|
f.read().decode('utf-8').splitlines(),
|
|
ps_regex(4),
|
|
ps_regex(4),
|
|
dict(
|
|
PID=lambda row: int(row['PID']),
|
|
RSS=lambda row: int(row['RSS']),
|
|
CPU=lambda row: float(row['%CPU']),
|
|
),
|
|
)
|
|
del t1['%CPU']
|
|
assert set(t1.keys()) == set(['PID', 'RSS', 'USER', 'CPU'])
|
|
|
|
t5 = subprocess.check_output(
|
|
'ps -e -o pid,args',
|
|
shell=True
|
|
).decode('utf-8').splitlines()
|
|
t6 = pandas_data_frame(
|
|
t5,
|
|
r'^\s*(\d+)\s(.*)$',
|
|
r'^\s+(\w+)\s+(\w+)\s*$',
|
|
dict(
|
|
PID=lambda row: int(row['PID'])
|
|
),
|
|
)
|
|
|
|
if not 'COMMAND' in t6:
|
|
if sys.platform == 'darwin' and 'ARGS' in t6:
|
|
t6['COMMAND'] = t6['ARGS']
|
|
del t6['ARGS']
|
|
else:
|
|
raise NotImplementedError
|
|
|
|
assert set(t6.keys()) == set(['PID', 'COMMAND'])
|
|
t11 = pandas_merge(t1, t6, on='PID')
|
|
t7 = pandas_filter_values(
|
|
t11,
|
|
lambda row: \
|
|
row['PID_x'] != self_pid and \
|
|
not 'freelancer' in row['COMMAND_y']
|
|
)
|
|
|
|
t8 = pandas_sort_values(
|
|
t7,
|
|
by=['RSS_x'],
|
|
ascending=False
|
|
)
|
|
t9 = pandas_sort_values(
|
|
t7,
|
|
by=['CPU_x'],
|
|
ascending=False
|
|
)
|
|
t10 = sum(t9['CPU_x'], 0.0) / 100
|
|
if options.debug:
|
|
pprint.pprint([t9['CPU_x'][:10], t10 * 100])
|
|
|
|
return dict(
|
|
by_mem=t8,
|
|
by_cpu=t9,
|
|
total_cpu=t10,
|
|
)
|
|
|
|
def oom_display_rows(current_dataframe):
|
|
print('\n'.join([
|
|
(
|
|
lambda row: \
|
|
'% 8d\t% 6.3f GiB\t% 5.2f %%\t% 10s\t%s' % (
|
|
row['PID_x'],
|
|
row['RSS_x'] / 1024 / 1024,
|
|
row['CPU_x'],
|
|
row['USER_x'],
|
|
row['COMMAND_y'],
|
|
)
|
|
)(
|
|
pandas_row(current_dataframe, k)
|
|
)
|
|
for k in range(
|
|
0,
|
|
min(
|
|
5,
|
|
pandas_shape(current_dataframe)[1],
|
|
)
|
|
)
|
|
]))
|
|
|
|
def oom_kill(pid):
|
|
assert isinstance(pid, int)
|
|
|
|
try:
|
|
logging.info('%s oom_kill, pid %d' % (
|
|
datetime.datetime.now().isoformat(),
|
|
pid,
|
|
))
|
|
os.kill(pid, signal.SIGKILL)
|
|
except:
|
|
logging.error(traceback.format_exc())
|
|
custom_notify(
|
|
msg='oom_kill, failed to kill pid %d' % pid
|
|
)
|
|
|
|
def oom_status():
|
|
print(
|
|
'\r%s %6.2f / %.2f %%, %6.2f / %.2f GiB' % (
|
|
datetime.datetime.now().isoformat(),
|
|
oom_mean_cpu() / os.cpu_count() * 100,
|
|
options.cpu_limit / os.cpu_count() * 100,
|
|
memory_stats()['mem_used'] / 1024 / 1024,
|
|
options.memory_limit / 1024 / 1024,
|
|
),
|
|
end=''
|
|
)
|
|
|
|
def first_check():
|
|
current_memory_stats = memory_stats()
|
|
|
|
t11 = oom_get_processes()
|
|
t8 = t11['by_mem']
|
|
|
|
if current_memory_stats['mem_used'] > options.memory_limit:
|
|
oom_display_rows(t8)
|
|
|
|
if t11['total_cpu'] > options.cpu_limit:
|
|
oom_display_rows(t11['by_cpu'])
|
|
|
|
free_before_oom = (
|
|
options.memory_limit - current_memory_stats['mem_used']
|
|
)
|
|
|
|
print(
|
|
'available %5.2f %% out of %5.2f %% of cpu limit before OOC' % (
|
|
(options.cpu_limit - t11['total_cpu']) * 100 / os.cpu_count(),
|
|
options.cpu_limit * 100 / os.cpu_count(),
|
|
)
|
|
)
|
|
|
|
print(
|
|
'%5.2f GiB [%5.2f %%] out of %5.2f GiB of free memory before OOM' % (
|
|
free_before_oom / 1024 / 1024,
|
|
free_before_oom / options.memory_limit * 100,
|
|
options.memory_limit / 1024 / 1024,
|
|
)
|
|
)
|
|
|
|
del t8
|
|
del t11
|
|
|
|
print('press Enter to start monitoring: ...', end='')
|
|
input()
|
|
print('\nstarted...')
|
|
|
|
first_check()
|
|
|
|
last_total_cpu = []
|
|
|
|
last_cpu_high = None
|
|
|
|
def oom_add_cpu(total_cpu):
|
|
if options.debug:
|
|
pprint.pprint([total_cpu, last_total_cpu])
|
|
last_total_cpu.append(total_cpu)
|
|
if len(last_total_cpu) > options.mean_size:
|
|
del last_total_cpu[-options.mean_size:]
|
|
|
|
def oom_mean_cpu():
|
|
return sum(last_total_cpu) / (len(last_total_cpu) + 1e-8)
|
|
|
|
def oom_cpu_high():
|
|
nonlocal last_cpu_high
|
|
|
|
if oom_mean_cpu() > options.cpu_limit:
|
|
if last_cpu_high is None:
|
|
last_cpu_high = datetime.datetime.now().timestamp()
|
|
|
|
if datetime.datetime.now().timestamp() - last_cpu_high > options.cpu_wait:
|
|
last_cpu_high = None
|
|
del last_total_cpu[:]
|
|
return True
|
|
|
|
return False
|
|
|
|
while True:
|
|
mem_used = memory_stats()['mem_used']
|
|
|
|
t11 = oom_get_processes()
|
|
|
|
oom_add_cpu(t11['total_cpu'])
|
|
|
|
t8 = t11['by_mem']
|
|
|
|
t9 = t8
|
|
t4 = lambda : oom_kill(t9['PID_x'][0])
|
|
|
|
t10 = lambda : mem_used > options.memory_limit
|
|
|
|
oom_status()
|
|
|
|
if t10():
|
|
print('\n', end='')
|
|
pprint.pprint([
|
|
'Killing [OOM]',
|
|
pandas_row(t9, 0),
|
|
mem_used,
|
|
])
|
|
t4()
|
|
|
|
if oom_cpu_high():
|
|
oom_display_rows(t11['by_cpu'])
|
|
print('\n', end='')
|
|
pprint.pprint([
|
|
'Killing [CPU]',
|
|
pandas_row(t11['by_cpu'], 0),
|
|
[options.cpu_limit, oom_mean_cpu(), t11['total_cpu']],
|
|
])
|
|
oom_kill(t11['by_cpu']['PID_x'][0])
|
|
time.sleep(options.period)
|
|
|
|
def resilient_vlc(stream=None):
|
|
if stream is None:
|
|
streams_path = os.path.join(
|
|
os.environ['CACHE_PATH'],
|
|
'resilient-vlc-streams.json'
|
|
)
|
|
|
|
if os.path.exists(streams_path):
|
|
with io.open(
|
|
streams_path,
|
|
'r'
|
|
) as f:
|
|
stream = json.load(f)
|
|
else:
|
|
raise RuntimeError(
|
|
'not found, %s' % streams_path
|
|
)
|
|
|
|
if isinstance(stream, str):
|
|
stream = [stream]
|
|
|
|
if len(stream) == 0:
|
|
raise RuntimeError('no streams')
|
|
|
|
import subprocess
|
|
import time
|
|
while True:
|
|
print('new start')
|
|
with subprocess.Popen([
|
|
'cvlc', '--verbose', '2', *stream,
|
|
], stderr=subprocess.PIPE) as p:
|
|
while p.returncode is None:
|
|
t1 = p.stderr.readline().decode('utf-8')
|
|
if len(t1) > 0:
|
|
print(t1)
|
|
if not all([
|
|
o in t1
|
|
for o in [
|
|
'prefetch stream error',
|
|
'terror',
|
|
'main interface error',
|
|
]
|
|
]) and any([
|
|
o in t1
|
|
for o in [
|
|
'pulse audio output debug: underflow'
|
|
]
|
|
]):
|
|
print('shit')
|
|
p.kill()
|
|
while True:
|
|
try:
|
|
t2 = p.wait(timeout=1)
|
|
print(t2)
|
|
break
|
|
except:
|
|
print('shit')
|
|
pass
|
|
time.sleep(1.0)
|
|
|
|
def sway_sock():
|
|
import glob
|
|
uid = os.stat(os.environ['HOME']).st_uid
|
|
t1 = glob.glob(
|
|
os.path.join(
|
|
'/run',
|
|
'user',
|
|
'%d' % uid,
|
|
'sway-ipc.%d*.sock' % uid,
|
|
)
|
|
)
|
|
t2 = [
|
|
os.stat(o).st_mtime
|
|
for o in t1
|
|
]
|
|
t3 = sorted(enumerate(t1), key=lambda x: t2[x[0]])[-1][0]
|
|
return t1[t3]
|
|
|
|
def eternal_firefox(
|
|
tabs=None,
|
|
profile=None,
|
|
group_name=None,
|
|
window_position=None,
|
|
debug=None,
|
|
):
|
|
import os
|
|
import datetime
|
|
import pprint
|
|
import subprocess
|
|
import time
|
|
if debug is None:
|
|
debug = False
|
|
if tabs is None:
|
|
raise RuntimeError('no tabs provided')
|
|
if profile is None:
|
|
raise RuntimeError('no profile provided')
|
|
if group_name is None:
|
|
raise RuntimeError('no group provided')
|
|
if window_position is None:
|
|
#window_position = '1,600,0,600,540'
|
|
raise RuntimeError('no window-position provided')
|
|
while True:
|
|
os.system(r'''date''')
|
|
with subprocess.Popen([
|
|
'firefox',
|
|
'-P', profile,
|
|
*tabs,
|
|
]) as p:
|
|
try:
|
|
if debug:
|
|
assert subprocess.check_call(['notify-send', '%s:Starting' % group_name]) == 0
|
|
|
|
#t3 = ''
|
|
for k in range(300):
|
|
t1 = subprocess.check_output(r'''
|
|
swaymsg -t get_tree | jq -r '..|try select(.pid== %d)'
|
|
''' % p.pid, shell=True).decode('utf-8')
|
|
if len(t1) > 10:
|
|
break
|
|
#time.sleep(0.1)
|
|
#t1 = subprocess.check_output(['wmctrl', '-p', '-l']).decode('utf-8')
|
|
#t4 = [o for o in t1.splitlines() if str(p.pid) in o]
|
|
#if len(t4) == 1:
|
|
# t3 = t4[0]
|
|
# break
|
|
|
|
#if t3 == '':
|
|
# raise RuntimeError
|
|
|
|
#t2 = t3.split()[0]
|
|
#assert os.system('wmctrl -i -r %s -e %s' % (t2, window_position)) == 0
|
|
#assert os.system('wmctrl -i -r %s -b add,below' % t2) == 0
|
|
def reposition():
|
|
t1 = lambda s: \
|
|
s \
|
|
.replace('{{PID}}', str(p.pid)) \
|
|
.replace('{{X}}', str(window_position[1])) \
|
|
.replace('{{Y}}', str(window_position[2])) \
|
|
.replace('{{W}}', str(window_position[3])) \
|
|
.replace('{{H}}', str(window_position[4])) \
|
|
.replace('{{WORKSPACE}}', str(window_position[0]))
|
|
|
|
assert os.system(t1(r'''
|
|
swaymsg '[pid="{{PID}}"] move window to workspace {{WORKSPACE}}'
|
|
''')) == 0
|
|
|
|
if window_position[1] != '' and window_position[2] != '':
|
|
assert os.system(t1(r'''
|
|
swaymsg '[pid="{{PID}}"] floating enable' \
|
|
swaymsg '[pid="{{PID}}"] resize set width {{W}}px height {{H}}px' && \
|
|
swaymsg '[pid="{{PID}}"] move absolute position {{X}}px {{Y}}px'
|
|
''')) == 0
|
|
else:
|
|
assert os.system(t1(r'''
|
|
swaymsg '[pid="{{PID}}"] floating disable'
|
|
''')) == 0
|
|
|
|
|
|
if False:
|
|
for tab in tabs[1:]:
|
|
time.sleep(10)
|
|
assert subprocess.check_call([
|
|
'firefox',
|
|
'-P', profile,
|
|
'--new-tab',
|
|
tab,
|
|
]) == 0
|
|
|
|
|
|
reposition()
|
|
|
|
if debug:
|
|
assert subprocess.check_call(['notify-send', '%s:Started' % group_name]) == 0
|
|
|
|
start = datetime.datetime.now()
|
|
is_to_restart = lambda : (datetime.datetime.now() - start).total_seconds() >= 900 * 4
|
|
polling_count = 0
|
|
|
|
while not is_to_restart():
|
|
if polling_count == 0:
|
|
reposition()
|
|
|
|
if not p.poll() is None:
|
|
break
|
|
time.sleep(10)
|
|
polling_count += 1
|
|
|
|
if debug:
|
|
assert subprocess.check_call(['notify-send', '%s:Closing' % group_name]) == 0
|
|
|
|
#assert os.system('wmctrl -i -c %s' % t2) == 0
|
|
assert os.system(r'''
|
|
swaymsg '[pid="%d"] kill'
|
|
''' % (p.pid,)) == 0
|
|
|
|
except KeyboardInterrupt:
|
|
assert os.system(r'''
|
|
swaymsg '[pid="%d"] kill'
|
|
''' % (p.pid,)) == 0
|
|
break
|
|
except:
|
|
import traceback
|
|
import pprint
|
|
pprint.pprint(traceback.format_exc())
|
|
finally:
|
|
try:
|
|
p.wait(20)
|
|
except subprocess.TimeoutExpired:
|
|
pprint.pprint([p.pid, '20 seconds timeout', 'kill'])
|
|
p.kill()
|
|
if debug:
|
|
assert subprocess.check_call(['notify-send', '%s:Closed' % group_name]) == 0
|
|
|
|
def resilient_ethernet(ip_addr, ethernet_device):
|
|
subprocess.check_call(
|
|
r'''
|
|
sudo sh -c '\
|
|
while true; \
|
|
do ping -c 3 -w 3 -W 1 {{IP_ADDR}} || (\
|
|
ip link set {{ETHERNET_DEVICE}} down; \
|
|
ip link set {{ETHERNET_DEVICE}} up; \
|
|
sleep 4; true;\
|
|
); \
|
|
sleep 10; clear; date; \
|
|
done'
|
|
'''.replace(
|
|
'{{IP_ADDR}}',
|
|
ip_addr
|
|
).replace(
|
|
'{{ETHERNET_DEVICE}}}',
|
|
ethernet_device
|
|
),
|
|
shell=True
|
|
)
|
|
|
|
def http_server(argv):
|
|
assert isinstance(argv, list) and all([isinstance(o, str) for o in argv])
|
|
parser = optparse.OptionParser()
|
|
parser.add_option(
|
|
'--public',
|
|
dest='public',
|
|
action='store_true',
|
|
default=False,
|
|
)
|
|
parser.add_option(
|
|
'--port',
|
|
dest='port',
|
|
type='int',
|
|
default=80,
|
|
)
|
|
parser.add_option(
|
|
'--host',
|
|
dest='host',
|
|
type='str',
|
|
default='127.0.0.1',
|
|
)
|
|
options, args = parser.parse_args(argv)
|
|
|
|
assert options.port >= 1
|
|
|
|
try:
|
|
assert not socket.inet_aton(options.host) is None
|
|
subprocess.check_call([
|
|
'ping', '-w', '1',
|
|
options.host
|
|
])
|
|
except:
|
|
raise RuntimeError('invalid ip address %s' % options.host)
|
|
|
|
|
|
index_section = 'autoindex on;'
|
|
if options.public:
|
|
location_section = 'location / {%s}' % index_section
|
|
else:
|
|
token = os.urandom(16).hex()
|
|
print(
|
|
'access url is http://%s:%d/%s/' % (
|
|
options.host,
|
|
options.port,
|
|
token,
|
|
)
|
|
)
|
|
location_section = (
|
|
'location / {'
|
|
'deny all;'
|
|
'}'
|
|
'location /%s/ {'
|
|
'alias /app/;'
|
|
'%s'
|
|
'}'
|
|
) % (token, index_section)
|
|
subprocess.check_call(
|
|
r'''
|
|
sudo docker run \
|
|
-p %s:%d:80 \
|
|
-u root \
|
|
-it --entrypoint=/bin/bash \
|
|
-v $PWD:/app:ro \
|
|
nginx:latest \
|
|
-c 'echo "server{listen 80; charset UTF-8; root /app; %s}" > /etc/nginx/conf.d/default.conf; nginx -g "daemon off;"'
|
|
''' % (
|
|
options.host,
|
|
options.port,
|
|
location_section,
|
|
),
|
|
shell=True)
|
|
|
|
def pass_ssh_osx(argv):
|
|
assert isinstance(argv, list) and all([isinstance(o, str) for o in argv])
|
|
parser = optparse.OptionParser()
|
|
parser.add_option(
|
|
'--list',
|
|
dest='list',
|
|
default=False,
|
|
action='store_true',
|
|
)
|
|
parser.add_option(
|
|
'--pass_option',
|
|
dest='pass_option',
|
|
action='append',
|
|
default=[],
|
|
type=str,
|
|
)
|
|
parser.add_option(
|
|
'--clipboard_copy',
|
|
dest='clipboard_copy',
|
|
default=None,
|
|
type=str,
|
|
)
|
|
parser.add_option(
|
|
'--debug',
|
|
dest='debug',
|
|
action='store_true',
|
|
default=False,
|
|
)
|
|
assert sys.platform in ['darwin', 'linux']
|
|
options, args = parser.parse_args(argv)
|
|
|
|
if options.clipboard_copy is None:
|
|
if sys.platform == 'linux':
|
|
options.clipboard_copy = 'wl-copy'
|
|
elif sys.platform == 'darwin':
|
|
options.clipboard_copy = 'pbcopy'
|
|
else:
|
|
raise NotImplementedError
|
|
|
|
if len(args) == 0:
|
|
raise RuntimeError('ssh_command is required')
|
|
|
|
if options.debug:
|
|
print(options.pass_option)
|
|
pprint.pprint(args)
|
|
|
|
|
|
|
|
reset_gpg_agent = r'''
|
|
gpgconf --kill gpg-agent && \
|
|
gpgconf --reload gpg-agent
|
|
'''
|
|
|
|
if not options.list:
|
|
t1 = options.pass_option
|
|
assert len(t1) > 0
|
|
|
|
print(
|
|
'select on of pass names\n%s' % '\n'.join([
|
|
'%d: %s' % (k, v)
|
|
for k, v in enumerate(t1)
|
|
])
|
|
)
|
|
|
|
while True:
|
|
try:
|
|
t2 = input()
|
|
t3 = int(t2)
|
|
assert t3 >= 0 and t3 < len(t1)
|
|
break
|
|
except:
|
|
continue
|
|
|
|
command = r'''
|
|
%s
|
|
gpg \
|
|
--pinentry-mode=ask \
|
|
-q -u $(cat ~/.password-store/.gpg-id) \
|
|
--decrypt \
|
|
~/.password-store/%s.gpg && \
|
|
echo -n '['$?']' && \
|
|
%s
|
|
''' % (
|
|
reset_gpg_agent,
|
|
t1[t3],
|
|
reset_gpg_agent,
|
|
)
|
|
else:
|
|
command = 'pass list | less -R'
|
|
|
|
ssh_command = [
|
|
'ssh', '-C',
|
|
'-o', 'ConnectTimeout 10',
|
|
'-o', 'ServerAliveInterval 1',
|
|
*args,
|
|
'-t',
|
|
command,
|
|
]
|
|
|
|
if options.debug:
|
|
pprint.pprint(
|
|
dict(
|
|
ssh_command=ssh_command,
|
|
)
|
|
)
|
|
|
|
if options.list:
|
|
subprocess.check_call(ssh_command)
|
|
else:
|
|
def clipboard_set(text):
|
|
with subprocess.Popen([
|
|
options.clipboard_copy,
|
|
], stdin=subprocess.PIPE) as p:
|
|
p.stdin.write(text.encode('utf-8'))
|
|
p.stdin.flush()
|
|
p.stdin.close()
|
|
p.wait(1)
|
|
assert p.poll() == 0
|
|
|
|
with subprocess.Popen(
|
|
ssh_command,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE
|
|
) as p:
|
|
password = None
|
|
last_chunk = None
|
|
|
|
hide_password = False
|
|
pinentry_delimeter = b'\x1b>'
|
|
|
|
def transform_callback(data):
|
|
nonlocal hide_password
|
|
nonlocal pinentry_delimeter
|
|
|
|
data2 = None
|
|
|
|
if not last_chunk is None:
|
|
data = last_chunk['data'] + data
|
|
|
|
if hide_password:
|
|
data2 = b''
|
|
elif pinentry_delimeter in data:
|
|
hide_password = True
|
|
pos = data.rfind(pinentry_delimeter)
|
|
if pos == -1:
|
|
data2 = data
|
|
else:
|
|
data2 = data[:pos + len(pinentry_delimeter)]
|
|
elif data == b'':
|
|
#return b'\r\n'
|
|
return b''
|
|
else:
|
|
data2 = None
|
|
|
|
return data2
|
|
|
|
for chunk in intercept_output(
|
|
current_subprocess=p,
|
|
return_aggregated=True,
|
|
transform_callback=transform_callback,
|
|
real_time=True,
|
|
#timeout=10,
|
|
):
|
|
if chunk['aggregated']:
|
|
last_chunk = chunk
|
|
break
|
|
|
|
assert not last_chunk is None
|
|
assert last_chunk['returncode'] == 0
|
|
|
|
if options.debug:
|
|
pprint.pprint(last_chunk['data'])
|
|
|
|
if last_chunk['data'].endswith('\r\n[0]'.encode('utf-8')) and \
|
|
last_chunk['data'].rfind(pinentry_delimeter) != -1:
|
|
last_line = last_chunk['data'].splitlines()[-2]
|
|
else:
|
|
raise RuntimeError(
|
|
'gpg failure %s' % str(
|
|
last_chunk['data'][
|
|
max(last_chunk['data'].find(pinentry_delimeter), -128):
|
|
]
|
|
)
|
|
)
|
|
|
|
pos2 = last_line.rfind(pinentry_delimeter)
|
|
if pos2 == -1:
|
|
last_line2 = last_line
|
|
else:
|
|
last_line2 = last_line[
|
|
pos2 + len(pinentry_delimeter):
|
|
]
|
|
|
|
password = last_line2.decode('utf-8').rstrip('\r\n')
|
|
assert not password is None
|
|
|
|
clipboard_set(password)
|
|
|
|
get_time = lambda : datetime.datetime.now().timestamp()
|
|
start = get_time()
|
|
while True:
|
|
cur = get_time()
|
|
remains = 10 - (cur - start)
|
|
if remains <= 1e-8:
|
|
break
|
|
else:
|
|
print('\r%5.2fs remains' % remains, end='')
|
|
time.sleep(0.1)
|
|
|
|
clipboard_set('')
|
|
print('\rcleared cliboard\n', end='')
|
|
|
|
def player_v1(folder_url, item_id):
|
|
import sys
|
|
import urllib.parse
|
|
import re
|
|
import subprocess
|
|
import os
|
|
import tqdm
|
|
t4 = folder_url
|
|
t1 = subprocess.check_output(['curl', '-s', t4]).decode('utf-8')
|
|
t2 = re.compile(r"href=\"(.*\.mp3)\"");
|
|
t3 = [o.group(1) for o in t2.finditer(t1)];
|
|
t5 = ['%s/%s' % (t4, o) for o in t3]
|
|
t6 = item_id
|
|
t9 = range(t6, len(t5))
|
|
with tqdm.tqdm(
|
|
total=len(t5),
|
|
) as progress_bar:
|
|
progress_bar.update(t6)
|
|
for k in t9:
|
|
t7 = t5[k]
|
|
t9 = urllib.parse.unquote(os.path.split(t7)[1])
|
|
progress_bar.set_description('%03d %s' % (k, t9))
|
|
with subprocess.Popen(['ffprobe', '-hide_banner', '-i', t7], stderr=subprocess.PIPE, stdout=subprocess.PIPE) as p:
|
|
p.wait()
|
|
assert p.returncode == 0
|
|
t8 = p.stderr.read().decode('utf-8')
|
|
#print(t8)
|
|
with subprocess.Popen(['ffplay', '-hide_banner', '-nodisp', '-autoexit', '-loop', '1', t7], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) as p:
|
|
p.wait()
|
|
assert p.returncode == 0
|
|
progress_bar.update(1)
|
|
|
|
def numpy_linspace(a, b, count):
|
|
pos = a
|
|
step = (b - a) / count
|
|
steps = []
|
|
|
|
for i in range(count):
|
|
if i == 0:
|
|
pos = a
|
|
elif i == count - 1:
|
|
pos = b
|
|
else:
|
|
pos = a + i * step
|
|
steps.append(pos)
|
|
|
|
return steps
|
|
|
|
def desktop_services(argv):
|
|
parser = optparse.OptionParser()
|
|
parser.add_option(
|
|
'--background_image',
|
|
dest='background_image',
|
|
default=None,
|
|
type=str,
|
|
)
|
|
parser.add_option(
|
|
'--cpufreq',
|
|
dest='cpufreq',
|
|
default=None,
|
|
type=int,
|
|
help='0 - mac book air (no turbo boost, max pct 30, every 4 seconds',
|
|
)
|
|
parser.add_option(
|
|
'--battery',
|
|
dest='battery',
|
|
default=None,
|
|
type=int,
|
|
help='0 - battery check with sleep <10%, every 10 seconds',
|
|
)
|
|
parser.add_option(
|
|
'--backlight-increase',
|
|
dest='backlight_increase',
|
|
default=False,
|
|
action='store_true',
|
|
help='increase keyboard backlight',
|
|
)
|
|
parser.add_option(
|
|
'--backlight-type',
|
|
dest='backlight_type',
|
|
default=[],
|
|
action='append',
|
|
help='backlight type, like keyboard, output',
|
|
)
|
|
parser.add_option(
|
|
'--backlight-decrease',
|
|
dest='backlight_decrease',
|
|
default=False,
|
|
action='store_true',
|
|
help='decrease keyboard backlight',
|
|
)
|
|
|
|
options, args = parser.parse_args(argv)
|
|
|
|
class Battery:
|
|
def __init__(self, should_start=None,):
|
|
if should_start is None:
|
|
should_start = False
|
|
|
|
assert isinstance(should_start, bool)
|
|
|
|
self.last_check = None
|
|
self.period = 10
|
|
self.is_running = should_start
|
|
|
|
def check_is_needed(self):
|
|
now = datetime.datetime.now(tz=datetime.timezone.utc)
|
|
|
|
is_needed = None
|
|
|
|
if self.last_check is None:
|
|
is_needed = True
|
|
else:
|
|
if ((now - self.last_check).total_seconds() >= self.period):
|
|
is_needed = True
|
|
else:
|
|
is_needed = False
|
|
|
|
if is_needed:
|
|
self.last_check = now
|
|
|
|
return is_needed
|
|
|
|
def run(self):
|
|
while True:
|
|
self.check()
|
|
|
|
time.sleep(self.period)
|
|
|
|
def terminate(self):
|
|
self.is_running = False
|
|
|
|
def wait(self, *args, **kwargs):
|
|
if self.is_running:
|
|
raise NotImplementedError
|
|
|
|
def poll(self):
|
|
if self.is_running:
|
|
return None
|
|
else:
|
|
return 0
|
|
|
|
def check(self):
|
|
try:
|
|
if not self.check_is_needed():
|
|
return
|
|
|
|
t1 = subprocess.check_output(
|
|
['upower', '-d'],
|
|
timeout=1,
|
|
).decode('utf-8')
|
|
t2 = [
|
|
o for o in t1.splitlines() if 'percentage' in o.lower()
|
|
]
|
|
t4 = [
|
|
o for o in t1.splitlines() if 'state' in o.lower()
|
|
]
|
|
t3 = float(t2[0].split(':')[1].strip()[:-1])
|
|
t5 = any(['discharging' in o.lower() for o in t4])
|
|
if t3 < 10 and t5:
|
|
logging.error(json.dumps(dict(
|
|
msg='too low', t3=t3, t5=t5
|
|
)))
|
|
subprocess.check_call(['systemctl', 'suspend'])
|
|
elif t3 < 15 and t5:
|
|
msg = 'battery near low'
|
|
logging.error(json.dumps(dict(
|
|
msg=msg, t3=t3, t5=t5
|
|
)))
|
|
subprocess.check_call([
|
|
'notify-send', '-t', '%d' % (5 * 1000), msg
|
|
])
|
|
else:
|
|
pass
|
|
print(
|
|
'\r%s % 5.2f%% %s' % (
|
|
datetime.datetime.now().isoformat(), t3, str(t5)
|
|
),
|
|
end=''
|
|
)
|
|
except:
|
|
print(traceback.format_exc())
|
|
|
|
class Backlight:
|
|
class Direction(enum.Enum):
|
|
increase = 'increase'
|
|
decrease = 'decrease'
|
|
absolute = 'absolute'
|
|
get_state = 'get_state'
|
|
|
|
class Mode(enum.Enum):
|
|
light = 'light'
|
|
|
|
def __init__(self):
|
|
self.state = []
|
|
self.dpms = Backlight.dpms_get()
|
|
|
|
@classmethod
|
|
def dpms_get(cls):
|
|
try:
|
|
t1 = subprocess.check_output(['swaymsg', '-r', '-t', 'get_outputs'])
|
|
t2 = t1.decode('utf-8')
|
|
t3 = json.loads(t2)
|
|
t4 = [
|
|
dict(
|
|
id=o['id'],
|
|
name=o['name'],
|
|
dpms=o['dpms'],
|
|
)
|
|
for o in t3
|
|
]
|
|
|
|
return any([o['dpms'] for o in t4])
|
|
except:
|
|
return True
|
|
|
|
def check(self):
|
|
try:
|
|
new_dpms = Backlight.dpms_get()
|
|
if new_dpms != self.dpms:
|
|
logging.info(json.dumps(dict(
|
|
module='backlight',
|
|
action='new_dpms',
|
|
dpms=self.dpms,
|
|
new_dpms=new_dpms,
|
|
)))
|
|
if new_dpms:
|
|
Backlight.enable(self.state)
|
|
else:
|
|
self.state = Backlight.change(
|
|
Backlight.Direction.get_state,
|
|
)
|
|
logging.info(json.dumps(dict(
|
|
state=pprint.pformat(
|
|
self.state,
|
|
width=1e+8,
|
|
compact=True,
|
|
),
|
|
action='disable',
|
|
)))
|
|
Backlight.disable()
|
|
self.dpms = new_dpms
|
|
except:
|
|
logging.error(traceback.format_exc())
|
|
|
|
@classmethod
|
|
def get_state(cls):
|
|
raise NotImplementedError
|
|
|
|
@classmethod
|
|
def set_state(cls):
|
|
raise NotImplementedError
|
|
|
|
@classmethod
|
|
def disable(cls):
|
|
return cls.change(
|
|
cls.Direction.absolute,
|
|
0,
|
|
)
|
|
|
|
@classmethod
|
|
def enable(cls, state,):
|
|
res = []
|
|
for device_state in state:
|
|
res.append(
|
|
cls.change(
|
|
direction=cls.Direction.absolute,
|
|
value=device_state['value'],
|
|
device_name=device_state['device_name'],
|
|
)
|
|
)
|
|
return res
|
|
|
|
@classmethod
|
|
def change(
|
|
cls,
|
|
direction,
|
|
value=None,
|
|
devices=None,
|
|
device_name=None,
|
|
types=None,
|
|
):
|
|
assert isinstance(direction, Backlight.Direction)
|
|
|
|
state = []
|
|
devices_all = dict(
|
|
smc_kbd=dict(
|
|
sysfs_path='sysfs/leds/smc::kbd_backlight',
|
|
),
|
|
intel_backlight=dict(
|
|
sysfs_path='sysfs/backlight/intel_backlight',
|
|
),
|
|
)
|
|
|
|
if devices is None:
|
|
devices = []
|
|
|
|
if not device_name is None:
|
|
devices.append(device_name)
|
|
|
|
if len(devices) == 0:
|
|
if types is None:
|
|
types = [
|
|
'keyboard',
|
|
'output',
|
|
]
|
|
|
|
for current_type in types:
|
|
if current_type == 'keyboard':
|
|
devices.extend([
|
|
'smc_kbd'
|
|
])
|
|
elif current_type == 'output':
|
|
devices.extend([
|
|
'intel_backlight',
|
|
])
|
|
else:
|
|
raise NotImplementedError
|
|
else:
|
|
assert types is None
|
|
|
|
devices2 = list(set(devices))
|
|
|
|
if sys.platform == 'linux':
|
|
assert all([
|
|
o in devices_all
|
|
for o in devices2
|
|
])
|
|
|
|
leds = \
|
|
[
|
|
o.strip()
|
|
for o in subprocess.check_output(
|
|
['light', '-L'],
|
|
timeout=1,
|
|
)\
|
|
.decode('utf-8')\
|
|
.splitlines()[1:]
|
|
]
|
|
|
|
|
|
for current_device_name in devices2:
|
|
device = devices_all[current_device_name]
|
|
|
|
sysfs_path = device['sysfs_path']
|
|
|
|
if not sysfs_path in leds:
|
|
raise NotImplementedError
|
|
|
|
extra_args = []
|
|
if value is None:
|
|
value = 20.0
|
|
|
|
value2 = max(float(value), 0.0)
|
|
|
|
assert isinstance(value2, float) and value >= -1e-8
|
|
|
|
if direction == cls.Direction.increase:
|
|
extra_args.extend(['-A', '%f' % value2])
|
|
elif direction == cls.Direction.decrease:
|
|
extra_args.extend(['-U', '%f' % value2])
|
|
elif direction == cls.Direction.absolute:
|
|
extra_args.extend(['-S', '%f' % value2])
|
|
elif direction == cls.Direction.get_state:
|
|
pass
|
|
else:
|
|
raise NotImplementedError
|
|
|
|
get_current = lambda : float(subprocess.check_output([
|
|
'light', '-G',
|
|
'-s', sysfs_path,
|
|
], timeout=1).decode('utf-8'))
|
|
|
|
if not (direction == cls.Direction.get_state):
|
|
old_value = get_current()
|
|
|
|
value_steps = None
|
|
|
|
if direction == cls.Direction.decrease:
|
|
value_steps = numpy_linspace(
|
|
old_value,
|
|
max(old_value - value2, 0),
|
|
10,
|
|
)
|
|
elif direction == cls.Direction.increase:
|
|
value_steps = numpy_linspace(
|
|
old_value,
|
|
min(old_value + value2, 100),
|
|
10,
|
|
)
|
|
elif direction == cls.Direction.absolute:
|
|
value_steps = numpy_linspace(
|
|
old_value,
|
|
min(
|
|
max(
|
|
0,
|
|
value2,
|
|
),
|
|
100
|
|
),
|
|
10,
|
|
)
|
|
else:
|
|
raise NotImplementedError
|
|
|
|
for current_value in value_steps:
|
|
subprocess.check_call(
|
|
[
|
|
'light', '-v', '3',
|
|
'-s', sysfs_path,
|
|
'-S', '%f' % current_value,
|
|
],
|
|
stderr=subprocess.PIPE,
|
|
stdout=subprocess.PIPE
|
|
)
|
|
time.sleep(0.05)
|
|
|
|
state.append(
|
|
dict(
|
|
mode=cls.Mode.light,
|
|
device_path=sysfs_path,
|
|
device_name=current_device_name,
|
|
value=get_current(),
|
|
)
|
|
)
|
|
else:
|
|
raise NotImplementedError
|
|
|
|
return state
|
|
|
|
if options.backlight_increase or options.backlight_decrease:
|
|
if options.backlight_increase:
|
|
direction = Backlight.Direction.increase
|
|
elif options.backlight_decrease:
|
|
direction = Backlight.Direction.decrease
|
|
else:
|
|
raise NotImplementedError
|
|
|
|
Backlight.change(
|
|
direction=direction,
|
|
types=options.backlight_type,
|
|
)
|
|
|
|
return
|
|
else:
|
|
pass
|
|
|
|
os.environ['SWAYSOCK'] = sway_sock()
|
|
|
|
assert all([
|
|
env_name in os.environ
|
|
for env_name in [
|
|
'GTK_IM_MODULE',
|
|
'XMODIFIERS',
|
|
'QT_IM_MODULE',
|
|
'I3SOCK',
|
|
'SWAYSOCK',
|
|
'WAYLAND_DISPLAY',
|
|
]
|
|
]) and os.environ['SWAYSOCK'] == sway_sock()
|
|
services = []
|
|
|
|
shutdown = False
|
|
|
|
def on_interrupt(*args, **kwargs):
|
|
nonlocal shutdown
|
|
shutdown = True
|
|
|
|
signal.signal(
|
|
signal.SIGINT,
|
|
on_interrupt,
|
|
)
|
|
signal.signal(
|
|
signal.SIGTERM,
|
|
on_interrupt,
|
|
)
|
|
|
|
try:
|
|
if options.cpufreq == 0:
|
|
print('launching cpufreq, need sudo')
|
|
subprocess.check_call(['sudo', 'whoami'])
|
|
|
|
services.append(
|
|
subprocess.Popen(r'''
|
|
exec sudo sh -c 'echo cpufreq, user; whoami;
|
|
while [[ -a /proc/{pid} ]]; do
|
|
echo passive > /sys/devices/system/cpu/intel_pstate/status;
|
|
echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo;
|
|
echo 40 > /sys/devices/system/cpu/intel_pstate/max_perf_pct;
|
|
for cpu_path in /sys/devices/system/cpu/cpu?; do
|
|
echo 900000 > $cpu_path/cpufreq/scaling_max_freq;
|
|
echo schedutil > $cpu_path/cpufreq/scaling_governor;
|
|
done;
|
|
sleep 10;
|
|
done;'
|
|
'''.format(pid=os.getpid()), shell=True)
|
|
)
|
|
|
|
class start_swayidle:
|
|
def __init__(self):
|
|
swaylock_cmd = [
|
|
'swaylock', '-f', '-d',
|
|
]
|
|
if not options.background_image is None:
|
|
swaylock_cmd.extend(
|
|
[
|
|
'-i',
|
|
'"%s"' % options.background_image,
|
|
]
|
|
)
|
|
subprocess.check_call([
|
|
'swaymsg',
|
|
'--',
|
|
'output',
|
|
'*',
|
|
'bg',
|
|
options.background_image,
|
|
'fill',
|
|
])
|
|
|
|
self.commands = dict(
|
|
swaylock_cmd2=' '.join(swaylock_cmd),
|
|
timeout1='echo timeout1; swaymsg "output * dpms off";',
|
|
lock='echo lock; pkill --signal SIGUSR1 swayidle;',
|
|
unlock='echo unlock; pkill --signal SIGINT swaylock; swaymsg "output * dpms on";',
|
|
unlock2='pkill --signal SIGINT swaylock;',
|
|
resume='echo resume; swaymsg "output * dpms on";',
|
|
before_sleep='echo before_sleep; loginctl lock-session;',
|
|
after_resume='echo after_resume; pkill --signal SIGUSR1 swayidle;',
|
|
)
|
|
self.last_force_idle = None
|
|
self.commands.update(
|
|
timeout2='echo timeout2; {swaylock_cmd};'.format(
|
|
swaylock_cmd=self.commands['swaylock_cmd2']
|
|
)
|
|
)
|
|
self.swayidle = subprocess.Popen(
|
|
r'''
|
|
exec swayidle -d -w \
|
|
timeout 300 'echo t1; read;' \
|
|
resume 'echo t5; ' \
|
|
timeout 900 'echo t4; read;' \
|
|
resume 'echo t5; ' \
|
|
lock 'echo t2; read;' \
|
|
unlock 'echo t3;' \
|
|
before-sleep 'echo t6; read;' \
|
|
after-resume 'echo t7; read;'
|
|
''',
|
|
shell=True,
|
|
stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE,
|
|
)
|
|
self.output = intercept_output(
|
|
self.swayidle,
|
|
real_time=True,
|
|
)
|
|
self.events = []
|
|
self.data = []
|
|
self.backlight = Backlight()
|
|
|
|
def poll(self):
|
|
return self.swayidle.poll()
|
|
|
|
def release_lock(self):
|
|
self.swayidle.stdin.write(b'\n')
|
|
self.swayidle.stdin.flush()
|
|
|
|
def force_idle(self):
|
|
if self.last_force_idle is None or (
|
|
datetime.datetime.now() - self.last_force_idle
|
|
).total_seconds() >= 10:
|
|
self.last_force_idle = datetime.datetime.now()
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def terminate(self, *args, **kwargs):
|
|
return self.swayidle.terminate(*args, **kwargs)
|
|
|
|
def wait(self, *args, **kwargs):
|
|
return self.swayidle.wait(*args, **kwargs)
|
|
|
|
def kill(self):
|
|
return self.swayidle.kill()
|
|
|
|
def dpms(self, direction):
|
|
assert direction in ['on', 'off']
|
|
|
|
raise NotImplementedError
|
|
|
|
def check(self):
|
|
while True:
|
|
if self.output is None:
|
|
break
|
|
|
|
chunk = next(self.output)
|
|
|
|
if chunk['aggregated']:
|
|
self.output = None
|
|
continue
|
|
|
|
if len(chunk['data']) == 0:
|
|
break
|
|
|
|
self.data.append(chunk)
|
|
|
|
if b'\n' in chunk['data']:
|
|
total = b''.join([
|
|
o['data']
|
|
for o in self.data
|
|
]).decode('utf-8')
|
|
sep_pos = total.rfind('\n')
|
|
lines = total[:sep_pos].splitlines()
|
|
self.data = [
|
|
dict(
|
|
data=total[sep_pos:].encode('utf-8'),
|
|
aggregated=False,
|
|
)
|
|
]
|
|
self.events.extend([
|
|
line
|
|
for line in lines
|
|
if line in [
|
|
't1', 't2', 't3', 't4',
|
|
't5', 't5', 't6', 't7',
|
|
] or (
|
|
'idle state' in line or \
|
|
'active state' in line
|
|
)
|
|
])
|
|
|
|
def retry(cb, cnt=None):
|
|
if cnt is None:
|
|
cnt = 10
|
|
|
|
i = 0
|
|
while True:
|
|
print('retry i = %d, cnt = %d' % (i, cnt))
|
|
|
|
if not (
|
|
subprocess.call(['swaymsg', '-t', 'get_version']) == 0
|
|
):
|
|
continue
|
|
|
|
if cb() == 0:
|
|
break
|
|
|
|
time.sleep(0.5)
|
|
|
|
i += 1
|
|
|
|
if len(self.events) > 0:
|
|
for o in self.events:
|
|
if o == 't1':
|
|
#if self.force_idle():
|
|
# subprocess.check_call(self.commands['lock'], shell=True)
|
|
print('started t1')
|
|
if self.force_idle():
|
|
subprocess.check_call(self.commands['timeout1'], shell=True)
|
|
print('done t1')
|
|
self.release_lock()
|
|
elif o == 't2':
|
|
print('started lock')
|
|
if self.force_idle():
|
|
subprocess.check_call(self.commands['lock'], shell=True)
|
|
subprocess.call(self.commands['timeout2'], shell=True)
|
|
subprocess.check_call(self.commands['timeout1'], shell=True)
|
|
print('done lock')
|
|
self.release_lock()
|
|
elif o == 't3':
|
|
pass
|
|
elif o == 't4':
|
|
print('started t4')
|
|
if self.force_idle():
|
|
subprocess.check_call(self.commands['lock'], shell=True)
|
|
subprocess.call(self.commands['timeout2'], shell=True)
|
|
subprocess.check_call(self.commands['timeout1'], shell=True)
|
|
print('done t4')
|
|
self.release_lock()
|
|
elif o == 't5':
|
|
print('started timeout resume')
|
|
if self.force_idle():
|
|
subprocess.check_call(self.commands['lock'], shell=True)
|
|
retry(
|
|
lambda: subprocess.call(self.commands['resume'], shell=True),
|
|
)
|
|
print('done timeout resume')
|
|
elif o == 't6':
|
|
print('started before-sleep')
|
|
if self.force_idle():
|
|
subprocess.call(self.commands['timeout2'], shell=True),
|
|
subprocess.check_call(self.commands['timeout1'], shell=True),
|
|
print('done before-sleep')
|
|
self.release_lock()
|
|
elif o == 't7':
|
|
print('started after-resume')
|
|
#if self.force_idle():
|
|
#subprocess.check_call(self.commands['lock'], shell=True)
|
|
while True:
|
|
if subprocess.call(
|
|
self.commands['resume'],
|
|
shell=True
|
|
) == 0:
|
|
break
|
|
else:
|
|
time.sleep(0.5)
|
|
print('done after-resume')
|
|
self.release_lock()
|
|
else:
|
|
raise NotImplementedError
|
|
|
|
pprint.pprint(self.events)
|
|
del self.events[:]
|
|
|
|
self.backlight.check()
|
|
|
|
services.extend([
|
|
subprocess.Popen(['ibus-daemon']),
|
|
start_swayidle(),
|
|
])
|
|
|
|
if not options.battery is None:
|
|
assert options.battery in [0]
|
|
|
|
print('launching battery')
|
|
services.append(
|
|
Battery(
|
|
should_start=True,
|
|
)
|
|
)
|
|
|
|
|
|
while True:
|
|
if shutdown:
|
|
print('shutdown')
|
|
break
|
|
|
|
if all([not o.poll() is None for o in services]):
|
|
print('done')
|
|
break
|
|
|
|
for o in services:
|
|
if hasattr(o, 'check'):
|
|
o.check()
|
|
|
|
time.sleep(0.1)
|
|
except:
|
|
logging.error(traceback.format_exc())
|
|
finally:
|
|
for o in services:
|
|
try:
|
|
o.terminate()
|
|
o.wait(timeout=10)
|
|
except:
|
|
logging.error(traceback.format_exc())
|
|
logging.error('killed %s' % str(o.__dict__))
|
|
o.kill()
|
|
|
|
|
|
def suspend_timer(argv):
|
|
import datetime;
|
|
import subprocess;
|
|
import time;
|
|
import sys;
|
|
if len(argv) == 0:
|
|
print("enter HH:MM");
|
|
t3 = input().strip()
|
|
else:
|
|
t3 = argv[0]
|
|
t2 = datetime.datetime.strptime(t3, "%H:%M").time()
|
|
while True:
|
|
t1 = datetime.datetime.now()
|
|
if ((t1.hour, t1.minute) >= (t2.hour, t2.minute)):
|
|
break
|
|
else:
|
|
t3 = [
|
|
(t2.hour - t1.hour),
|
|
t2.minute - t1.minute
|
|
]
|
|
if t3[1] < 0:
|
|
t3[1] += 60
|
|
t3[0] -= 1
|
|
print("\r%s, %02d:%02d" % (
|
|
t1,
|
|
*t3,
|
|
), end="")
|
|
time.sleep(1)
|
|
print("suspend computer at %s" % t1.isoformat())
|
|
subprocess.check_call(["systemctl", "suspend"]);
|
|
|
|
def socat_ssh(argv):
|
|
parser = optparse.OptionParser()
|
|
parser.add_option(
|
|
'--local_port',
|
|
dest='local_port',
|
|
default=None,
|
|
type=int,
|
|
)
|
|
parser.add_option(
|
|
'--ssh_key',
|
|
dest='ssh_key',
|
|
default=None,
|
|
type=str,
|
|
)
|
|
parser.add_option(
|
|
'--socat_verbose',
|
|
dest='socat_verbose',
|
|
action='store_true',
|
|
default=False,
|
|
)
|
|
parser.add_option(
|
|
'--ssh_host',
|
|
dest='ssh_host',
|
|
default=None,
|
|
type=str,
|
|
)
|
|
parser.add_option(
|
|
'--target_port',
|
|
dest='target_port',
|
|
default=None,
|
|
type=int,
|
|
)
|
|
parser.add_option(
|
|
'--ssh_command',
|
|
dest='ssh_command',
|
|
default=None,
|
|
type=str,
|
|
)
|
|
parser.add_option(
|
|
'--gateway_command',
|
|
dest='gateway_command',
|
|
default=None,
|
|
type=str,
|
|
help=(
|
|
'a shell command that forwards ssh socket data '
|
|
'somewhere else, like '
|
|
'busybox nc 127.0.0.1 $(cat remote-ssh.port)'
|
|
),
|
|
)
|
|
options, args = parser.parse_args(argv)
|
|
|
|
if options.ssh_command is None:
|
|
ssh_command = ['ssh', '-T', '-C']
|
|
else:
|
|
ssh_command = options.ssh_command.split()
|
|
|
|
if not options.ssh_key is None:
|
|
subprocess.check_call(['ssh-add', options.ssh_key])
|
|
ssh_command.extend([
|
|
'-i', options.ssh_key,
|
|
])
|
|
|
|
if not options.ssh_host is None:
|
|
ssh_command.extend([options.ssh_host])
|
|
|
|
restart = False
|
|
|
|
def on_interrupt(*args, **kwargs):
|
|
nonlocal restart
|
|
restart = True
|
|
|
|
|
|
socat_command = ['socat']
|
|
|
|
if options.socat_verbose:
|
|
socat_command.extend(['-v'])
|
|
|
|
socat_command.extend([
|
|
'tcp-listen:%d,fork,bind=127.0.0.1' % (
|
|
options.local_port,
|
|
),
|
|
])
|
|
|
|
signal.signal(
|
|
signal.SIGINT,
|
|
on_interrupt,
|
|
)
|
|
signal.signal(
|
|
signal.SIGTERM,
|
|
on_interrupt,
|
|
)
|
|
|
|
gateway = None
|
|
p = None
|
|
|
|
while True:
|
|
if gateway is None:
|
|
gateway = tempfile.NamedTemporaryFile(suffix='.sh', mode='w')
|
|
gateway.write(
|
|
r'''
|
|
exec %s
|
|
''' % ' '.join(
|
|
ssh_command + [options.gateway_command]
|
|
)
|
|
)
|
|
gateway.flush()
|
|
|
|
if p is None:
|
|
p = subprocess.Popen(
|
|
socat_command + [
|
|
'EXEC:sh %s' % gateway.name,
|
|
]
|
|
)
|
|
|
|
time.sleep(1)
|
|
|
|
if restart:
|
|
try:
|
|
p.terminate()
|
|
p.wait(10)
|
|
except:
|
|
p.kill()
|
|
|
|
restart = False
|
|
|
|
if not p.poll() is None:
|
|
p = None
|
|
|
|
if not gateway is None:
|
|
os.path.unlink(gateway.name)
|
|
if not p is None:
|
|
p.terminate()
|
|
|
|
|
|
def share_wifi(argv):
|
|
parser = optparse.OptionParser()
|
|
parser.add_option(
|
|
'--to-wifi',
|
|
dest='to_wifi',
|
|
default=None,
|
|
type=str,
|
|
)
|
|
parser.add_option(
|
|
'--from-eth',
|
|
dest='from_eth',
|
|
default=None,
|
|
type=str,
|
|
)
|
|
parser.add_option(
|
|
'--ap-name',
|
|
dest='ap_name',
|
|
default=None,
|
|
type=str,
|
|
)
|
|
parser.add_option(
|
|
'--restart-delay',
|
|
dest='restart_delay',
|
|
default=None,
|
|
type=int,
|
|
)
|
|
options, args = parser.parse_args(argv)
|
|
|
|
if options.restart_delay is None:
|
|
options.restart_delay = 2
|
|
|
|
|
|
assert not options.to_wifi is None
|
|
assert not options.from_eth is None
|
|
assert not options.ap_name is None
|
|
assert options.restart_delay >= 1
|
|
|
|
print('enter password:')
|
|
|
|
pw = subprocess.check_output(
|
|
'read -s PW; echo -n $PW',
|
|
shell=True
|
|
).decode('utf-8')
|
|
if len(pw) == 0:
|
|
p2 = subprocess.check_output(
|
|
'pwgen -syn 20 1',
|
|
shell=True
|
|
).decode('utf-8')
|
|
with subprocess.Popen(
|
|
['qrencode', '-t', 'UTF8'],
|
|
stdin=subprocess.PIPE
|
|
) as p:
|
|
p.stdin.write(pw.encode('utf-8'))
|
|
p.stdin.flush()
|
|
p.stdin.close()
|
|
try:
|
|
p.wait(5)
|
|
except Exception as exception:
|
|
p.kill()
|
|
raise exception
|
|
|
|
last_timestamp = datetime.datetime.now()
|
|
hostapd = None
|
|
restart = False
|
|
|
|
def on_interrupt(*args, **kwargs):
|
|
nonlocal restart
|
|
restart = True
|
|
|
|
signal.signal(
|
|
signal.SIGINT,
|
|
on_interrupt,
|
|
)
|
|
signal.signal(
|
|
signal.SIGTERM,
|
|
on_interrupt,
|
|
)
|
|
|
|
while True:
|
|
try:
|
|
if hostapd is None:
|
|
print('\n%s, start new' % last_timestamp)
|
|
hostapd = subprocess.Popen([
|
|
'create_ap',
|
|
'--hostapd-timestamps',
|
|
options.to_wifi,
|
|
options.from_eth,
|
|
options.ap_name,
|
|
pw,
|
|
])
|
|
else:
|
|
if restart:
|
|
print('\n%s, shutdown current' % last_timestamp)
|
|
os.kill(
|
|
hostapd.pid,
|
|
signal.SIGINT
|
|
)
|
|
try:
|
|
hostapd.wait(20)
|
|
except:
|
|
hostapd.terminate()
|
|
restart = False
|
|
|
|
if not hostapd.poll() is None:
|
|
hostapd = None
|
|
|
|
if (
|
|
datetime.datetime.now() - last_timestamp
|
|
).total_seconds() > options.restart_delay:
|
|
restart = True
|
|
|
|
last_timestamp = datetime.datetime.now()
|
|
except:
|
|
print(traceback.format_exc())
|
|
restart = True
|
|
finally:
|
|
time.sleep(1)
|
|
|
|
def status(argv):
|
|
import inspect
|
|
import textwrap
|
|
|
|
assert isinstance(argv, list) and all([isinstance(o, str) for o in argv])
|
|
|
|
class c1(optparse.IndentedHelpFormatter):
|
|
def format_option(self, *args, **kwargs):
|
|
f1 = lambda text, width: '\n'.join([
|
|
textwrap.fill('\t' + o, width, replace_whitespace=False)
|
|
for o in text.splitlines()
|
|
]).splitlines()
|
|
t1 = inspect.getsource(optparse.IndentedHelpFormatter.format_option)
|
|
t2 = '\n'.join([o[4:] for o in t1.splitlines()[:]]).replace(
|
|
'textwrap.wrap', 'f1',
|
|
).replace('format_option', 'f2')
|
|
exec(t2, dict(f1=f1), locals())
|
|
return locals()['f2'](self, *args, **kwargs)
|
|
|
|
parser = optparse.OptionParser(
|
|
formatter=c1(
|
|
width=None,
|
|
),
|
|
)
|
|
parser.add_option(
|
|
'--sh',
|
|
dest='sh',
|
|
default=[],
|
|
action='append',
|
|
type=str,
|
|
)
|
|
parser.add_option(
|
|
'--timeout',
|
|
dest='timeout',
|
|
default=None,
|
|
type=float,
|
|
)
|
|
parser.add_option(
|
|
'--config',
|
|
dest='config',
|
|
default=None,
|
|
type=str,
|
|
help=''.join([
|
|
'.json file with array of strings, each is a shell command ',
|
|
'that outputs a separate status text value, ',
|
|
'like\n',
|
|
r'''
|
|
ping -w 1 -i 0.02 <hostname> -c 3 | tail -n 2| head -n 1 | grep -Po $'time\\s+.*$'
|
|
sensors -j | jq -r '.\"coretemp-isa-0000\".\"Package id 0\".temp1_input|tostring + \" C\"'
|
|
printf '%d RPM' $(cat /sys/devices/platform/applesmc.768/fan1_input)
|
|
printf '% 3.0f%%' $(upower -d | grep -Po 'percentage:\\s+\\d+(\\.\\d+)?%' | grep -Po '\\d+(\\.\\d+)?' | head -n 1)
|
|
'''.strip()
|
|
])
|
|
)
|
|
options, args = parser.parse_args(argv)
|
|
|
|
if options.timeout is None:
|
|
options.timeout = 0.5
|
|
|
|
timeout2 = max(options.timeout, 0.0)
|
|
|
|
assert timeout2 >= 0.0 and timeout2 <= 4
|
|
|
|
config = dict()
|
|
try:
|
|
if not options.config is None:
|
|
with io.open(options.config, 'r') as f:
|
|
config.update(
|
|
json.load(f)
|
|
)
|
|
except:
|
|
logging.error(traceback.format_exc())
|
|
pass
|
|
|
|
options.sh.extend(
|
|
config.get('sh', [])
|
|
)
|
|
|
|
t1 = []
|
|
|
|
for sh_index, o in enumerate([
|
|
*options.sh,
|
|
*[
|
|
r'''
|
|
free -h | \
|
|
grep -P Mem: | grep -Po '[\w\.\d]+' | tail -n +2 | head -n 3 | xargs echo -n;
|
|
''',
|
|
r'''
|
|
date +'%Y-%m-%d %l:%M:%S %p';
|
|
''',
|
|
],
|
|
]):
|
|
try:
|
|
t1.append(
|
|
subprocess.check_output(
|
|
o,
|
|
shell=True,
|
|
timeout=timeout2,
|
|
).decode('utf-8').strip()
|
|
)
|
|
except:
|
|
t1.append('fail %d' % sh_index)
|
|
|
|
t3 = ' | '.join(t1).replace('\n\r', '')
|
|
|
|
sys.stdout.write(t3)
|
|
sys.stdout.flush()
|
|
|
|
def custom_translate(current_string, check, none_char=None,):
|
|
if none_char is None:
|
|
none_char = '.'
|
|
|
|
class A:
|
|
def __getitem__(self, k):
|
|
t1 = chr(k)
|
|
|
|
t2 = check(k, t1)
|
|
if isinstance(t2, bool):
|
|
if t2:
|
|
return t1
|
|
else:
|
|
return none_char
|
|
elif isinstance(t2, str):
|
|
return t2
|
|
return current_string.translate(
|
|
A()
|
|
)
|
|
|
|
def media_keys(argv):
|
|
assert isinstance(argv, list) and all([isinstance(o, str) for o in argv])
|
|
parser = optparse.OptionParser()
|
|
parser.add_option(
|
|
'--command',
|
|
dest='command',
|
|
type=str,
|
|
default=None,
|
|
)
|
|
|
|
options, args = parser.parse_args(argv)
|
|
|
|
if options.command is None and len(args) > 0:
|
|
assert len(args) == 1
|
|
options.command = args[0]
|
|
|
|
assert options.command in [
|
|
'media-play-pause',
|
|
'media-next',
|
|
'media-prev',
|
|
'media-lower-volume',
|
|
]
|
|
|
|
msg = None
|
|
|
|
mode = None
|
|
is_mocp = lambda : \
|
|
subprocess.call([
|
|
'pgrep',
|
|
'-u', os.environ['USER'],
|
|
'mocp',
|
|
], stdout=subprocess.PIPE) == 0
|
|
|
|
def mocp_info():
|
|
t1 = subprocess.check_output(['mocp', '-i'])
|
|
t3 = t1.decode('utf-8')
|
|
t2 = dict([
|
|
tuple(o.split(':')[:2])
|
|
for o in t3.splitlines()
|
|
])
|
|
return t2['Title'].strip()[:128]
|
|
|
|
if is_mocp:
|
|
mode = 'mocp'
|
|
else:
|
|
mode = 'playerctl'
|
|
|
|
if options.command == 'media-play-pause':
|
|
if mode == 'mocp':
|
|
subprocess.check_call(['mocp', '--toggle-pause'])
|
|
msg = mocp_info()
|
|
elif mode == 'playerctl':
|
|
subprocess.check_call(['playerctl', 'play-pause'])
|
|
msg = player_metadata()
|
|
else:
|
|
raise NotImplementedError
|
|
elif options.command == 'media-next':
|
|
if mode == 'mocp':
|
|
subprocess.check_call(['mocp', '--next'])
|
|
msg = mocp_info()
|
|
elif mode == 'playerctl':
|
|
subprocess.check_call(['playerctl', 'next'])
|
|
msg = player_metadata()
|
|
else:
|
|
raise NotImplementedError
|
|
elif options.command == 'media-prev':
|
|
if mode == 'mocp':
|
|
subprocess.check_call(['mocp', '--previous'])
|
|
msg = mocp_info()
|
|
elif mode == 'playerctl':
|
|
subprocess.check_call(['playerctl', 'previous'])
|
|
msg = player_metadata()
|
|
else:
|
|
raise NotImplementedError
|
|
elif options.command == 'media-lower-volume':
|
|
subprocess.check_call([
|
|
'pactl',
|
|
'set-sink-volume',
|
|
'@DEFAULT_SINK@',
|
|
'-5%'
|
|
])
|
|
msg = subprocess.check_output([
|
|
'pactl',
|
|
'get-sink-volume',
|
|
'@DEFAULT_SINK@'
|
|
]).decode('utf-8').strip()
|
|
elif options.command == 'media-raise-volume':
|
|
subprocess.check_call([
|
|
'pactl',
|
|
'set-sink-volume',
|
|
'@DEFAULT_SINK@',
|
|
'+5%'
|
|
])
|
|
msg = subprocess.check_output([
|
|
'pactl',
|
|
'get-sink-volume',
|
|
'@DEFAULT_SINK@'
|
|
]).decode('utf-8').strip()
|
|
else:
|
|
raise NotImplementedError
|
|
|
|
logging.info(
|
|
json.dumps(
|
|
dict(
|
|
command=options.command,
|
|
msg=msg,
|
|
mode=mode,
|
|
),
|
|
ensure_ascii=False
|
|
)
|
|
)
|
|
|
|
return dict(
|
|
msg=msg,
|
|
)
|
|
|
|
def commands_cli():
|
|
logging.getLogger().setLevel(logging.INFO)
|
|
|
|
msg = None
|
|
|
|
try:
|
|
if sys.argv[1].startswith('media'):
|
|
msg = media_keys(sys.argv[1:]).get('msg')
|
|
elif sys.argv[1] == 'status':
|
|
status(sys.argv[2:])
|
|
elif sys.argv[1] == 'http-server':
|
|
http_server(sys.argv[2:])
|
|
elif sys.argv[1] == 'pass-ssh-osx':
|
|
pass_ssh_osx(sys.argv[2:])
|
|
elif sys.argv[1] == 'wl-screenshot':
|
|
subprocess.check_call(r'''
|
|
grim -g "$(slurp)" - | wl-copy
|
|
''', shell=True)
|
|
elif sys.argv[1] == 'chrome':
|
|
chrome(sys.argv[2:])
|
|
elif sys.argv[1] == 'eternal-oom':
|
|
eternal_oom(sys.argv[2:])
|
|
elif sys.argv[1] == 'resilient-vlc':
|
|
resilient_vlc(sys.argv[2:])
|
|
elif sys.argv[1] == 'eternal-firefox':
|
|
eternal_firefox(
|
|
profile=sys.argv[2],
|
|
group_name=sys.argv[3],
|
|
window_position=json.loads(sys.argv[4]),
|
|
debug=json.loads(sys.argv[5]),
|
|
tabs=sys.argv[6:],
|
|
)
|
|
elif sys.argv[1] == 'resilient-ethernet':
|
|
resilient_ethernet(
|
|
ip_addr=sys.argv[2],
|
|
ethernet_device=sys.argv[3],
|
|
)
|
|
elif sys.argv[1] == 'player':
|
|
player_v1(
|
|
folder_url=sys.argv[2],
|
|
item_id=int(sys.argv[3]),
|
|
)
|
|
elif sys.argv[1] == 'share-wifi':
|
|
share_wifi(sys.argv[2:])
|
|
elif sys.argv[1] == 'socat-ssh':
|
|
socat_ssh(sys.argv[2:])
|
|
elif sys.argv[1] == 'sway_sock':
|
|
print(sway_sock())
|
|
elif sys.argv[1] == 'suspend-timer':
|
|
suspend_timer(sys.argv[2:])
|
|
elif sys.argv[1] == 'desktop-services':
|
|
desktop_services(sys.argv[2:])
|
|
else:
|
|
raise NotImplementedError
|
|
except SystemExit:
|
|
pass
|
|
except:
|
|
msg = 'not implemented\n%s' % traceback.format_exc()
|
|
logging.error(msg)
|
|
|
|
if not msg is None:
|
|
custom_notify(msg=msg)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
commands_cli()
|