#!/usr/bin/env python3
import asyncio
import enum
import threading
import shutil
import collections
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


logger = logging.getLogger(__name__)


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,
    need_lines=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 = []
    buffer = collections.deque()
    buffer_lines = collections.deque()

    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)
            if need_lines:
                buffer.append(t3)

            if need_lines:
                if b'\n' in t3:
                    t3_pos = t3.rfind(b'\n')
                    buffer_lines.extend([
                        o + b'\n'
                        for o in b''.join(
                            list(buffer)[:-1] + [
                                t3[:t3_pos]
                            ],
                        ).splitlines()
                    ])
                    buffer.clear()
                    buffer.append(t3[t3_pos + 1:])
                while len(buffer_lines) > 0:
                    yield dict(
                        aggegated=False,
                        line=buffer_lines.popleft(),
                    )

            else:
                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

            if len(t6) > 0:
                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(
        '--cpu',
        dest='cpu_json',
        type=str,
        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)

    if not options.cpu_json is None:
        options.cpu = json.loads(options.cpu_json)
    else:
        options.cpu = True

    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(extra_filter=None,):
        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')
        if extra_filter is None:
            extra_filter = lambda *args : True

        t7 = pandas_filter_values(
            t11,
            lambda row: \
                row['PID_x'] != self_pid and \
                not 'freelancer' in row['COMMAND_y'] and \
                extra_filter(row)
        )

        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(cpu_limit=None):
        if cpu_limit is None:
            cpu_limit = options.cpu_limit

        nonlocal last_cpu_high

        if oom_mean_cpu() > 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

    mem_used = None
    mem_stat = None

    def oom_mem_high(memory_limit=None):
        nonlocal mem_used

        if memory_limit is None:
            memory_limit = options.memory_limit

        return mem_used > memory_limit

    while True:
        mem_stat = memory_stats()
        mem_used = mem_stat['mem_used']

        if options.memory_limit < mem_stat['mem_total'] and not oom_mem_high(
            mem_stat['mem_total'] - (
                mem_stat['mem_total'] - options.memory_limit
            ) / 2
        ):
            extra_filters = lambda row: (
                'chrome' in row['COMMAND_y'] and '--type=renderer' in row['COMMAND_y']
                or not 'chrome' in row['COMMAND_y']
            )
        else:
            extra_filters = None

        t11 = oom_get_processes(extra_filters)

        oom_add_cpu(t11['total_cpu'])

        t8 = t11['by_mem']

        t9 = t8
        t4 = lambda : oom_kill(t9['PID_x'][0])

        oom_status()

        if oom_mem_high():
            print('\n', end='')
            pprint.pprint([
                'Killing [OOM]',
                pandas_row(t9, 0),
                mem_used,
            ])
            t4()

        if options.cpu and 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(
        '--force',
        dest='force',
        action='store_true',
        default=False,
    )
    parser.add_option(
        '--token',
        dest='token',
        type=str,
        default=None,
    )
    parser.add_option(
        '--port',
        dest='port',
        type='int',
        default=80,
    )
    parser.add_option(
        '--no_docker',
        dest='docker',
        action='store_false',
        default=None,
    )
    parser.add_option(
        '-H', '--header',
        dest='response_headers',
        type='str',
        action='append',
        default=[],
    )
    parser.add_option(
        '--host',
        dest='host',
        type='str',
        default='127.0.0.1',
    )
    parser.add_option(
        '--prefix',
        dest='prefix',
        type='str',
        default=None,
    )
    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)


    if options.docker is None:
        options.docker = True

    index_section = 'autoindex on;'

    if options.public:
        options.token = 'public'

        location_section = 'location / {%s}' % index_section
    else:
        if options.token is None:
            options.token = os.urandom(16).hex()

    if options.docker:
        DATA_DIR = '/usr/share/nginx'
        APP_DIR = '/app'
        CONF_DIR = '/etc/nginx'
    else:
        DATA_DIR = '/opt/nginx/%s/data/' % options.token
        CONF_DIR = '/opt/nginx/%s/conf/' % options.token
        APP_DIR = os.path.abspath(os.path.curdir)

    if not options.public:
        if not options.prefix is None:
            path = options.prefix + options.token
        else:
            path = options.token

        logger.info(
            'access url is http://%s:%d/%s/' % (
                options.host,
                options.port,
                path,
            )
        )

        assert all([
            not re.compile(
                r'^[A-Za-z-]+ [a-z0-9A-Z-\.]+$'
            ).match(o) is None
            for o in options.response_headers
        ])

        location_section = (
            'location / {'
            'deny all;'
            '}'
            'location /%s/ {'
            'alias %s/;'
            '%s'
            '%s'
            '}'
        ) % (
            path,
            APP_DIR,
            '\n'.join([
                'add_header %s;' % o
                for o in options.response_headers
            ]),
            index_section
        )

    if options.docker:
        subprocess.check_call(
            r'''
                sudo docker run \
                    -p %s:%d:80 \
                    -u root \
                    -it --entrypoint=/bin/bash \
                    -v $PWD:%s:ro \
                    --log-driver none \
                    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,
                APP_DIR,
                location_section,
            ),
            shell=True
        )
    else:
        if os.path.exists(CONF_DIR):
            assert options.force
            shutil.rmtree(CONF_DIR)

        if os.path.exists(DATA_DIR):
            assert options.force
            shutil.rmtree(DATA_DIR)

        os.makedirs(CONF_DIR, exist_ok=True)
        os.makedirs(
            os.path.join(
                CONF_DIR,
                'conf.d',
            ),
            exist_ok=True
        )
        os.makedirs(DATA_DIR, exist_ok=True)

        with io.open(
            os.path.join(CONF_DIR, 'nginx.conf'),
            'w'
        ) as f:
            f.write(r'''

events {
    multi_accept on;
    worker_connections 32;
}

http {
  include /etc/nginx/mime.types;
  default_type application/octet-stream;
  expires off;
  open_file_cache off;

  log_format main
    '[$time_local][$remote_addr, $http_x_forwarded_for, $t1, $server_name]'
    '[$request_length,$bytes_sent,$request_time]'
    '[$status][$request]'
    '[$http_user_agent][$http_referer]';

  access_log /dev/null combined;
  access_log /dev/stderr main;

  include %s/conf.d/*.conf;

  map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
  }
}
            ''' % CONF_DIR)

        with io.open(
            os.path.join(
                CONF_DIR,
                'conf.d',
                'default.conf',
            ),
            'w',
        ) as f:
            f.write(r'''
server {
  server_name %s;
  listen %d;
  charset UTF-8;
  client_max_body_size 2K;

  set $t1 $remote_addr;
  if ($http_x_forwarded_for)
  {
    set $t1 $http_x_forwarded_for;
  }

  root %s;
  %s
}
            ''' % (
                options.host,
                options.port,
                DATA_DIR,
                location_section,
            ))

        sys.stderr.flush()
        sys.stdout.flush()

        os.execv(
          '/usr/sbin/nginx',
          [
            '/usr/sbin/nginx',
            '-c',
            os.path.join(CONF_DIR, 'nginx.conf'),
            '-p', DATA_DIR,
            '-g', 'daemon off;',
          ],
        )

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,
        help='pass secret path, like --pass_option google.com/login/password/v1',
    )
    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 pm_service(argv):
    parser = optparse.OptionParser()
    parser.add_option(
        '--events',
        dest='events',
        default=[],
        action='append',
        help='pb,tp,kb',
    )
    parser.add_option(
        '--verbose',
        dest='verbose',
        type=str,
        default=None,
        help='true,false',
    )
    options, args = parser.parse_args(argv)

    if options.verbose is None:
        options.verbose = False
    else:
        val = json.loads(options.verbose)
        assert isinstance(val, bool)
        options.verbose = val

    if len(options.events) == 0:
        options.events.extend([
            'pb',
            #'tp', 'kb'
        ])

    assert all([
        o in [
            'pb','tp', 'kb'
        ]
        for o in options.events
    ])

    assert sys.platform == 'darwin'

    wu = 0

    while True:
        subprocess.check_call([
            'osascript',
            '-e',
            'tell application "Finder" to sleep'
        ])
        subprocess.check_call(
            ['pmset','sleepnow'],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )

        wu += 1


        sample = (
            r'2024-03-19 22:00:36.808589+0300 0x4caa7    Default     0x0                  102'
            r'0    powerd: [com.apple.powerd:assertions] Process WindowServer.156 TurnedOn '
            r'UserIsActive "com.apple.iohideventsystem.queue.tickle serviceID:10000267f '
            r'service:AppleMultitouchDevice product:Apple Internal Keyboard / Trackpad '
            r'eventType:11" age:00:00:00  id:38654742889 [System: PrevIdle DeclUser kDisp]'
        )

        action = None
        with subprocess.Popen(['log', 'stream'], stdout=subprocess.PIPE) as p:
            for chunk in intercept_output(
                p,
                return_aggregated=False,
                need_lines=True,
                transform_callback=lambda x: b'',
            ):
                line = chunk['line'].decode('utf-8')
                #p.stdout.readline().decode('utf-8')
                cmd = None
                if 'powerd' in line:
                    cmd = line
                    if options.verbose:
                        logging.error(json.dumps(dict(line=cmd)))

                #cmd = subprocess.check_output(r'''
                #    log stream | grep --line-buffered -i \
                #    -E 'powerd.*TurnedOn.*UserIsActive' | head -n 1
                #''', shell=True).decode('utf-8')

                if not cmd is None and (
                    'TurnedOn' in cmd or
                    'PrevIdle' in cmd or
                    'PMRD: kIOMessageSystemWillPowerOn' in cmd
                ):
                    if (
                        ('AppleMultitouchDevice' in cmd and 'tp' in options.events) or
                        ('AppleACPIButton' in cmd and 'pb' in options.events) or
                        ('eventType:29' in cmd and 'kb' in options.events)
                    ):
                        action = 'wake-up'
                        break
                    else:
                        action = 'sleep'
                        break


        if options.verbose:
            logging.error(json.dumps(dict(cmd=cmd, action=action,)))
        else:
            print('\r%s wu : %d, la : %s' % (
                datetime.datetime.now().isoformat(),
                wu,
                action
            ), end='')

        if action == 'wake-up':
            break
        elif action == 'sleep':
            continue
        else:
            raise NotImplementedError

    print('')

def scrap_yt_music(argv):
    parser = optparse.OptionParser()
    parser.add_option(
        '--verbose',
        dest='verbose',
        type=str,
        default=None,
        help='true,false',
    )
    parser.add_option(
        '-l',
        '--library_path',
        dest='library_path',
        type=str,
        default=None,
    )
    options, args = parser.parse_args(argv)

    if options.library_path is None:
        options.library_path = os.path.abspath(os.path.curdir)

    if options.verbose is None:
        options.verbose = False
    else:
        val = json.loads(options.verbose)
        assert isinstance(val, bool)
        options.verbose = val

    import aiohttp.web

    def http_events(context, res_cb):
        data = []

        async def handle(request):
            data.append(request.rel_url.query.copy())

            res_cb(event=data[-1], events=data)
            if len(data) > 128:
                del data[:128]

            return aiohttp.web.Response(text='ok')

        async def serve():
            logging.info('http_events starting')
            app = aiohttp.web.Application()
            app.add_routes([aiohttp.web.get('/status', handle)])
            runner = aiohttp.web.AppRunner(app, handle_signals=False,)
            await runner.setup()
            site = aiohttp.web.TCPSite(runner, host='127.0.0.1', port=8877)
            await site.start()

            logging.info('http_events started')

            while True:
                await asyncio.sleep(1)
                if context['shutdown']:
                    break

            await runner.cleanup()

            logging.info('http_events done')

        asyncio.run(serve())

        #aiohttp.web.run_app(
        #    app,
        #    host='127.0.0.1',
        #    port=8877,
        #    handle_signals=False,
        #)

        #while True:
        #    data.append(
        #        subprocess.check_output(r'''
        #            nc -w 1 -l 127.0.0.1 8877 | head -n 1
        #        ''', shell=True,)
        #    )

    def audio_recorder(context):
        current_name = None

        p = None

        try:
            while True:
                with context['track_cv']:
                    context['track_cv'].wait(1)

                if context['track_name'] != current_name:
                    logging.info('audio_record, track changed, started')
                    if not p is None:
                        logging.info('audio_record, track changed, terminating')
                        p.terminate()
                        p.wait()
                        p = None
                        logging.info('audio_record, track changed, terminated')
                    current_name = context['track_name']

                if context['shutdown']:
                    if not p is None:
                        p.terminate()
                    break

                if p is None and not current_name is None:
                    output_name = os.path.join(
                        options.library_path,
                        '%s.mp3' % current_name
                    )
                    logging.info('audio_record, new recording')
                    p = subprocess.Popen(
                        ['sox', '-d', output_name],
                        stdout=subprocess.DEVNULL,
                        stdin=subprocess.DEVNULL,
                        stderr=subprocess.DEVNULL,
                    )
                    logging.info(json.dumps(dict(output_name=output_name)))
        except:
            logging.error(traceback.format_exc())
        finally:
            if not p is None:
                p.terminate()

    context = dict(
        http_on_event=lambda *args, **kwargs: None,
        shutdown=False,
        workers=[],
        track_cv=threading.Condition(),
        main_cv=threading.Condition(),
        track_name=None,
    )

    context['workers'].extend([
        threading.Thread(
            target=functools.partial(
                http_events,
                context=context,
                res_cb=lambda *args, **kwargs: context['http_on_event'](*args, **kwargs),
            )
        ),
        threading.Thread(
            target=functools.partial(
                audio_recorder,
                context=context,
            )
        ),
    ])

    def http_on_event(event, events):
        with context['track_cv']:
            if 'title' in event and event['title'].strip() != '':
                context['track_name'] = str(event['title'])[:128].replace('\n', '')
            else:
                context['track_name'] = None

        logging.info(event)

    context['http_on_event'] = http_on_event

    print(r'''
        https://github.com/ExistentialAudio/BlackHole/wiki/Multi-Output-Device#5-set-audio-output-to-multi-output-device

        Open Youtube Music,
        and launch the following JS script:
        ```js


(function(){
  let timer = null;
  let is_first = true;
  let last_play = null;
  function last_play_update() {last_play = (new Date());}
  let should_stop = () => (((new Date()).valueOf() - last_play.valueOf()) > 5 * 1000);
  timer = setInterval(() => {
    let is_playing = () => $$('#play-pause-button')[0].title == 'Pause';
    let title = () => encodeURIComponent(
      $$('.ytmusic-player-bar.middle-controls')[0].innerText
    );
    let update_status = (query) => fetch('http://127.0.0.1:8877/status?' + query);

    if (is_playing())
    {
      last_play_update();
      is_first = false;
      update_status('title=' + title());
    }
    else if (!is_first)
    {
      update_status('')
      if (should_stop())
      {
        console.log('should stop');
        clearInterval(timer);
      }
    }
  }, 1000);
})();

        ```
    ''')

    for w in context['workers']:
        w.start()

    #context['main_cv'] = threading.Condition()

    def on_interrupt(*args, **kwargs):
        logging.info('on_interrupt')
        with context['main_cv']:
            context['main_cv'].notify()

    signal.signal(
        signal.SIGINT,
        on_interrupt,
    )
    signal.signal(
        signal.SIGTERM,
        on_interrupt,
    )

    with context['main_cv']:
        context['main_cv'].wait()

    with context['main_cv']:
        context['shutdown'] = True
        context['main_cv'].notify()
    with context['track_cv']:
        context['track_cv'].notify()

    for o in context['workers']:
        o.join()

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',
    )
    parser.add_option(
        '--backlight_service',
        dest='backlight_service',
        action='store_true',
        default=False,
        help='enable backlight_service',
    )

    options, args = parser.parse_args(argv)

    class VLC:
        @classmethod
        def vlc_is_playing_fullscreen(cls):
            import subprocess
            import json
            import sys
            import pprint

            t2 = []
            try:
                t1 = subprocess.check_output(['swaymsg', '-t', 'get_tree']).decode('utf-8')
                t2 = json.loads(t1)
            except:
                logging.error(traceback.format_exc())

            def walk(o, cb):
                if isinstance(o, dict):
                    cb(o)
                    for k, v in o.items():
                        walk(v, cb,)
                elif isinstance(o, list):
                    cb(o)
                    for o2 in o:
                        walk(o2, cb,)
                else:
                    cb(o)

            t3 = []

            walk(t2, lambda o: [
                t3.append(o)
                if isinstance(o, dict) and \
                    'fullscreen_mode' in o and \
                    o['fullscreen_mode'] == 1 and \
                    'window_properties' in o and \
                    'class' in o['window_properties'] and \
                    o['window_properties']['class'] == 'vlc'
                else None
            ])

            t4 = False

            try:
                t4 = subprocess.check_output([
                    'playerctl', '-p', 'vlc', 'status'
                ], timeout=1,).decode('utf-8').strip() == 'Playing'
            except:
                logging.error(traceback.format_exc())

            #pprint.pprint(t3)

            return len(t3) > 0 and t4


    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:
                logging.error(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'],
                    timeout=1
                )
                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):
        logging.info('blah')
        nonlocal shutdown
        shutdown = True

    signal.signal(
        signal.SIGINT,
        on_interrupt,
    )
    signal.signal(
        signal.SIGTERM,
        on_interrupt,
    )

    try:
        if options.cpufreq == 0:
            logging.info('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,
                    stdout=subprocess.DEVNULL,
                    stderr=subprocess.DEVNULL,
                    stdin=subprocess.DEVNULL,
                )
            )

        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,
                        ]
                    )

                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;' 2>&1
                    ''',
                    shell=True,
                    stdin=subprocess.PIPE,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.DEVNULL,
                    restore_signals=False,
                    preexec_fn=lambda : os.setpgrp(),
                )
                self.output = intercept_output(
                    self.swayidle,
                    real_time=True,
                    transform_callback=lambda x: [logging.info(x), b''][-1],
                )
                self.events = []
                self.last_skip_loop = None
                self.data = []
                if options.backlight_service:
                    self.backlight = Backlight()
                else:
                    self.backlight = None
                self.bg = None
                self.bg_terminate = False

            def skip_loop_long_ago(self):
                if self.last_skip_loop is None or (
                    datetime.datetime.now() - self.last_skip_loop
                ).total_seconds() >= 30:
                    self.last_skip_loop = datetime.datetime.now()
                    return True
                else:
                    return False

            def background_check(self):
                if (
                    self.bg is None or \
                    not self.bg.poll() is None
                ) and not self.bg_terminate:
                    self.bg = subprocess.Popen([
                        'swaybg',
                        '--output',
                        '*',
                        '--image',
                        options.background_image,
                        '--mode',
                        'fill',
                    ])

            def background_terminate(self, *args, **kwargs):
                if not self.bg is None:
                    self.bg_terminate = True
                    self.bg.terminate(*args, **kwargs)

            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):
                self.background_terminate()

                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):
                new_events = []

                class event_t(enum.Enum):
                    idle_state = 'idle state'
                    active_state = 'active state'

                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,
                            )
                        ]
                        for line in lines:
                            if event_t.idle_state.value in line:
                                line = event_t.idle_state.value
                            elif event_t.active_state.value in line:
                                line = event_t.active_state.value
                            else:
                                pass

                            if line in [
                                't1', 't2', 't3', 't4',
                                't5', 't5', 't6', 't7',
                                event_t.idle_state.value,
                                event_t.active_state.value,
                            ]:
                                new_events.append(line)

                def retry(cb, cnt=None):
                    if cnt is None:
                        cnt = 10

                    i = 0
                    while True:
                        logging.info('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(new_events) > 0 or \
                    len(self.events) > 0 and \
                    self.skip_loop_long_ago()
                ):
                    self.events.extend(new_events)

                    skip_loop = False

                    if (
                        all([
                            o in ['t1', 't4']
                            for o in self.events
                        ]) and \
                        VLC.vlc_is_playing_fullscreen() and \
                        self.backlight.dpms
                    ):
                        skip_loop = True
                        logging.info(
                            'skip loop, %s' % (
                                [
                                    json.dumps(self.events),
                                    self.backlight.dpms,
                                    VLC.vlc_is_playing_fullscreen(),
                                    self.events,
                                    new_events,
                                ],
                            )
                        )
                    elif (
                        len(new_events) == 0 and \
                        len(self.events) > 1 and \
                        all([
                            o in ['t1', 't4']
                            for o in self.events
                        ])
                    ):
                        self.events = ['t4']
                    elif len(self.events) > 1 and (
                        self.events == ['t1', 't4', 't5', 't5'] or \
                        self.events == ['t1', 't5', 't5'] or \
                        self.events == ['t1', 't5']
                    ):
                        for o in new_events:
                            self.release_lock()

                        self.events = []

                    for o in self.events:
                        if skip_loop:
                            self.release_lock()
                            continue

                        if o == 't1':
                            #if self.force_idle():
                            #    subprocess.check_call(self.commands['lock'], shell=True)
                            logging.info('started t1')
                            if self.force_idle():
                                subprocess.check_call(self.commands['timeout1'], shell=True)
                            logging.info('done t1')
                            self.release_lock()
                        elif o == 't2':
                            logging.info('started lock')
                            if self.force_idle():
                                custom_notify(
                                    title='swayidle',
                                    msg='loginctl lock started',
                                )
                                while True:
                                    if not subprocess.call(
                                        self.commands['lock'], shell=True
                                    ) == 0:
                                        continue
                                    if not subprocess.call(
                                        self.commands['timeout2'], shell=True
                                    ) == 0:
                                        #continue
                                        pass
                                    if not subprocess.call(
                                        self.commands['timeout1'], shell=True
                                    ) == 0:
                                        continue
                                    break
                            logging.info('done lock')
                            self.release_lock()
                        elif o == 't3':
                            pass
                        elif o == 't4':
                            logging.info('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)
                            logging.info('done t4')
                            self.release_lock()
                        elif o == 't5':
                            logging.info('started timeout resume')
                            if self.force_idle():
                                subprocess.check_call(self.commands['lock'], shell=True)
                            retry(
                                lambda: subprocess.call(self.commands['resume'], shell=True),
                            )
                            logging.info('done timeout resume')
                        elif o == 't6':
                            logging.info('started before-sleep')
                            if self.force_idle():
                                subprocess.call(self.commands['timeout2'], shell=True),
                                subprocess.check_call(self.commands['timeout1'], shell=True),
                            logging.info('done before-sleep')
                            self.release_lock()
                        elif o == 't7':
                            logging.info('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)
                            logging.info('done after-resume')
                            self.release_lock()
                        elif o in [
                            event_t.idle_state.value,
                            event_t.active_state.value,
                        ]:
                            logging.info(json.dumps(dict(o=o)))
                        else:
                            logging.error(json.dumps(dict(o=o)))
                            raise NotImplementedError

                    if not skip_loop:
                        pprint.pprint(self.events)
                        del self.events[:]

                if not self.backlight is None:
                    self.backlight.check()

                self.background_check()

        services.extend([
            subprocess.Popen(
                ['ibus-daemon'],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                stdin=subprocess.DEVNULL,
            ),
            start_swayidle(),
        ])

        if not options.battery is None:
            assert options.battery in [0]

            logging.info('launching battery')
            services.append(
                Battery(
                    should_start=True,
                )
            )


        while True:
            if shutdown:
                logging.info('shutdown')
                break

            if all([not o.poll() is None for o in services]):
                logging.info('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(
        '--channel',
        dest='channel',
        default=None,
        type=int,
    )
    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
    shutdown = False

    def on_interrupt(sig, *args, **kwargs):
        nonlocal restart
        if sig == signal.SIGINT:
            restart = True
        elif sig  == signal.SIGTERM:
            shutdown = True
        else:
            raise NotImplementedError

    signal.signal(
        signal.SIGINT,
        on_interrupt,
    )
    signal.signal(
        signal.SIGTERM,
        on_interrupt,
    )

    hostapd_args = [
        'create_ap',
        '--hostapd-timestamps',
        options.to_wifi,
        options.from_eth,
        options.ap_name,
        pw,
    ]
    if not options.channel is None:
        hostapd_args.extend(['-c', '%d' % options.channel])

    while True:
        try:
            if hostapd is None:
                print('\n%s, start new' % last_timestamp)
                hostapd = subprocess.Popen(hostapd_args)
            else:
                if restart or shutdown:
                    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 shutdown:
                break

            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'''
                A=$(free -h | grep -P Mem: | grep -Po '[\w\.\d]+');
                echo -n $A | awk '{print $2, $7}';
            ''',
            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)
    logger.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:])
        elif sys.argv[1] == 'pm-service':
            pm_service(sys.argv[2:])
        elif sys.argv[1] == 'scrap-yt-music':
            scrap_yt_music(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()
