aboutsummaryrefslogtreecommitdiff
path: root/scripts/sway-toolwait
blob: 562cc499e2bb74386015d9812da503385d3bd3c8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/usr/bin/env -S python3 -B
# https://gitlab.com/wef/dotfiles/-/blob/master/bin/sway-toolwait
TIME_STAMP="20230321.220111"

# dependencies: i3ipc: https://i3ipc-python.readthedocs.io/en/latest/

# Copyright (C) 2020-2022 Bob Hepple <bob dot hepple at gmail dot com>

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from i3ipc import Connection
import argparse
import subprocess, sys
import time
from multiprocessing import Process

sys.dont_write_bytecode = True

global parser, i3, retval, args, start_time

def verbose(msg):
    if args.verbose:
        elapsed_time = time.time() - start_time
        print(f"{elapsed_time}s: {parser.prog}: {msg}")

def sleep_and_run_command():
    "This runs in a separate process"
    s = 0.1 # just enough to let i3ipc.main() loop start
    verbose(f"Runner process sleeping for {s}s")
    time.sleep(s)
    verbose(f"Running: {args.command}")
    # perhaps a simple fork/spawn here would suffice (and be quicker?)
    subprocess.Popen(args.command)

def on_window(i3, e):
    "callback function from i3ipc.main() loop"
    verbose(f"Got a {e.change} window event:")
    if e.ipc_data['container']['app_id']:
        verbose(f"app_id: {e.ipc_data['container']['app_id']}")
    else:
        verbose(f"Class: {e.ipc_data['container']['window_properties']['class']}, instance: {e.ipc_data['container']['window_properties']['instance']}")

    waitfor = args.command[0]
    if args.waitfor:
        waitfor = args.waitfor

    finished = False
    if args.nocheck:
        finished = True
    else:
        if e.ipc_data['container']['app_id']:
            new_window = e.ipc_data['container']['app_id']
        else:
            new_window = e.ipc_data['container']['window_properties']['instance']
        if new_window == waitfor:
            verbose(f"a new window for '{waitfor}' appeared")
            finished = True
        else:
            verbose(f"a new window appeared '{new_window}' but I'm waiting for '{waitfor}'")

    if finished:
        i3.main_quit() # else continue to wait

if __name__ == "__main__":

    parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
        description="""
Run 'command', wait for a window to open, then exit. If no window
appears in 'timeout' seconds (eg by running a non-GUI program like
'date') then terminate.
""",
        epilog="""eg.

    %(prog)s firefox # this gives time for the window to be created before:
    swaymsg -q "floating disable; border none"

To run more complex commands use "--". eg.

    %(prog)s -- bash -c "some complex bash commands"

This may be similar to the ancient Sun OpenWindows command toolwait or
the X11 version at
http://www.ibiblio.org/pub/linux/X11/xutils/toolwait-0.9.tar.gz
    """)
    parser.add_argument('-n', '--nocheck', dest='nocheck', action='store_true',
                        help='don\'t check that the window that opens is for that command (default = %(default)s)')
    parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', help='verbose operation')
    parser.add_argument('-m', '--mark', dest='mark', type=str, help='mark to add to window', default='')
    parser.add_argument('-t', '--timeout', dest='timeout', type=float, help='timeout (default = %(default)s secs)', default=30.0)
    parser.add_argument('-w', '--waitfor', dest='waitfor', help='app_id (wayland) or instance string (xwayland) to wait for')
    parser.add_argument('command', nargs='+', help='command to run')
    args = parser.parse_args()

    i3 = Connection()
    i3.on('window::new', on_window)

    retval = 1
    start_time = time.time()
    # run the command without waiting ie in background:
    Process(target=sleep_and_run_command).start()
    i3.main(timeout=args.timeout)

    # i3ipc gives no indication that a timeout has occured, so check the clock:
    elapsed_time = time.time() - start_time
    if elapsed_time <= args.timeout:
        verbose(f"'{args.command} took {elapsed_time}secs to create its first window")
        retval = 0
    else:
        verbose(f"timed out after {args.timeout} secs")

    if args.mark != '':
        mark_cmd = f'swaymsg -q mark "{args.mark}"'
        verbose(f"Running: {mark_cmd}")
        subprocess.run([mark_cmd], shell=True)

    verbose(f"terminated, retval={retval}")
    sys.exit(retval)

# Local variables:
# mode: python
# time-stamp-pattern: "4/TIME_STAMP=\"%:y%02m%02d.%02H%02M%02S\""
# eval: (add-hook 'before-save-hook 'time-stamp)
# End: