Example #1
0
def get_current_workspace(
        conn: i3ipc.Connection) -> Tuple[i3ipc.WorkspaceReply, object]:
    """ Get the focused workspace.

        Returns
        -------
        A tuple in form (workspace, con), with the focused workspace and
        the container object for the focused workspace, respectively.
    """
    ws = [w for w in conn.get_workspaces() if w['focused']][0]
    tree = conn.get_tree()
    ws_tree = [c for c in tree.descendents()
               if c.type == 'workspace' and c.name == ws['name']][0]

    return ws, ws_tree
Example #2
0
def render_apps(i3: i3ipc.Connection, ws: Optional[int]):
    tree = i3.get_tree()
    wss = i3.get_workspaces()
    visible_ws = [ws.name for ws in wss if ws.visible]

    apps = tree.leaves()
    apps = [app for app in apps if app.workspace().name in visible_ws]
    if ws is not None:
        apps = [
            app for app in apps if (int(app.workspace().name) - 1) % 3 == ws
        ]
    apps.sort(key=lambda app: app.workspace().name)

    out = '%{O12}'.join(format_entry(app) for app in apps)

    print(out, flush=True)
Example #3
0
#!/usr/bin/python
import sys
from i3ipc import Connection, Event

i3 = Connection()

next_num = next(i for i in range(1, 100)
                if not [ws for ws in i3.get_workspaces() if int(ws.name) == i])

i3.command(f'workspace number {next_num};')
Example #4
0
#!/usr/bin/env python3
#
# Promotes the focused window by swapping it with the largest window.

from i3ipc import Connection, Event


def find_biggest_window(container):
    max_leaf = None
    max_area = 0
    for leaf in container.leaves():
        rect = leaf.rect
        area = rect.width * rect.height
        if not leaf.focused and area > max_area:
            max_area = area
            max_leaf = leaf
    return max_leaf


i3 = Connection()

for reply in i3.get_workspaces():
    if reply.focused:
        workspace = i3.get_tree().find_by_id(reply.ipc_data["id"])
        master = find_biggest_window(workspace)
        i3.command("swap container with con_id %s" % master.id)
Example #5
0
#!/usr/bin/env python3
from i3ipc import Connection

# Lil util to flatten 2d list
flatten = lambda t: [item for sublist in t for item in sublist]

i3 = Connection()

visible_workspaces = list(filter(lambda ws: ws.visible, i3.get_workspaces()))
focused_workspace = list(filter(lambda ws: ws.focused, visible_workspaces))[0]
named_workspace = i3.get_tree().find_named(focused_workspace.name)
workspace_index = list(map(lambda ws: ws.name,
                           named_workspace)).index(focused_workspace.name)

floating_windows = list(
    filter(lambda window: window.floating == 'user_on',
           named_workspace[workspace_index].leaves()))
floating_index = list(map(lambda win: win.focused,
                          floating_windows)).index(True)

next_win = floating_windows[(floating_index + 1) % len(floating_windows)]
next_win.command('focus')
Example #6
0
#!/usr/bin/env python3

from i3ipc import Connection

i3 = Connection()

scratch = i3.get_tree().scratchpad().leaves()

# If there is something in the scratchpad
# Lists are true if not empty
if scratch:
    #switch to workspace 42 and making the windows stack
    i3.command("workspace 42")
    i3.command("layout stacking")

    #remove each window from scratchpad and defloat it
    for window in scratch:
        window.command("scratchpad show")
        window.command("floating toggle")
else:
    #make list of used workspaces
    workspaces = i3.get_workspaces()
    wsinuse = []

    for i in workspaces:
        wsinuse.append(i.num)

    #if workspace 42 is already in use, switch to it
    if 42 in wsinuse:
        i3.command("workspace 42")
class WorkSpacer:

    def __init__(self, args):
        self.i3 = None
        self.args = args
        self.workspaces_on_outputs = {}
        self.workspaces = None
        self.outputs = None
        self.config = None
        self.mouse = pynput.mouse.Controller()
        self.mouse_position = None
        self.current_output_name = None

    def _connect(self):
        self.print_if_debug('All available outputs on device')
        self.print_if_debug(names_for_outputs)
        try:
            self.i3 = Connection()
            self.config = self.i3.get_config().__dict__['config']
            config_outputs = {}
            for matchNo, match in enumerate(
                    re.finditer(r'set (\$[a-zA-Z0-9]+) (' + names_for_outputs + ')',
                                self.config, re.MULTILINE), start=1
            ):
                config_outputs[match.group(1)] = match.group(2)
            self.print_if_debug('All outputs listed in the config, matched on available configs')
            self.print_if_debug(config_outputs)
            config_workspace_names = {}
            for matchNum, match in enumerate(
                    re.finditer(r'set (\$.*) (\d.*)', self.config, re.MULTILINE)
            ):
                config_workspace_names[match.group(1)] = match.group(2)
            self.print_if_debug('All config_workspaces_names')
            self.print_if_debug(config_workspace_names)

            for matchNum, match in enumerate(
                    re.finditer(r'workspace (\$.*) output (\$.*)', self.config, re.MULTILINE)
            ):
                if not config_outputs.keys().__contains__(match.group(2)):
                    continue  # Not an active display, skip it
                if not self.workspaces_on_outputs.keys().__contains__(config_outputs[match.group(2)]):
                    self.workspaces_on_outputs[config_outputs[match.group(2)]] = []
                self.workspaces_on_outputs[config_outputs[match.group(2)]].append(
                    config_workspace_names[match.group(1)])
            self.print_if_debug("All workspaces with outputs")
            self.print_if_debug(self.workspaces_on_outputs)

        except Exception as exc:
            if self.args.debug:
                raise exc
            sys.exit(1)
        self.workspaces = [workspaces for workspaces in self.i3.get_workspaces()]
        outputs = self.i3.get_outputs()
        self.outputs = [output for output in outputs if output.__dict__["active"] is True]

    def run(self):
        self._connect()
        self.mouse_position = self.mouse.position
        self.current_output_name = self._get_workspace_from_courser_position()

        if self.args.shift:
            self.i3.command(
                f'move container to workspace {self.workspaces_on_outputs[self.current_output_name][self.args.index - 1]}')
            if not self.args.keep_with_it:
                return
        self.i3.command(f'workspace {self.workspaces_on_outputs[self.current_output_name][self.args.index - 1]}')

    def _get_workspace_from_courser_position(self):
        for output in self.outputs:
            width = output.__dict__["rect"].__dict__["width"]
            height = output.__dict__["rect"].__dict__["height"]
            x_offset = output.__dict__["rect"].__dict__["x"]
            y_offset = output.__dict__["rect"].__dict__["y"]

            if x_offset == 0 and y_offset == 0:
                if x_offset <= self.mouse_position[0] <= x_offset + width and y_offset <= \
                        self.mouse_position[1] <= y_offset + height:
                    return output.__dict__["name"]
            elif x_offset == 0:
                if x_offset <= self.mouse_position[0] <= x_offset + width and y_offset < \
                        self.mouse_position[1] <= y_offset + height:
                    return output.__dict__["name"]
            elif y_offset == 0:
                if x_offset < self.mouse_position[0] <= x_offset + width and y_offset <= \
                        self.mouse_position[1] <= y_offset + height:
                    return output.__dict__["name"]
            else:
                if x_offset < self.mouse_position[0] <= x_offset + width and y_offset < \
                        self.mouse_position[1] <= y_offset + height:
                    return output.__dict__["name"]

    def _get_workspaces_for_output(self, output):
        return [workspace for workspace in self.workspaces if workspace.__dict__['output'] == output]

    def print_if_debug(self, to_print):
        if self.args.debug:
            pprint.pprint(to_print)
Example #8
0
import i3ipc.events as events
import time, os, subprocess

sway = Connection()


def show_notification(text, timeout=1000):
    proc = subprocess.Popen(['notify-send', text, '-t', str(timeout)])
    proc.wait(2)


def on_ws_init(_, e: events.WorkspaceEvent):
    print('ws init', e.current.name)

    # Start VS Code on workspace 2
    if e.current.name == '2' and os.system('pgrep code') != 0:
        show_notification('Launching VS Code...', 2000)
        sway.command('exec code')

    # Start Firefox on workspace 3
    if e.current.name == '3' and os.system('pgrep firefox') != 0:
        show_notification('Launching Firefox...', 3000)
        sway.command('exec firefox')


sway.on(Event.WORKSPACE_INIT, on_ws_init)

sway.get_workspaces()

sway.main()
Example #9
0
class Heimdall(QObject):
    """Heimdall service

    This service maintains a Raspberry Pi or similar device as an automatic display that works without user interaction.

    Connection timeline:
        0. _setup_reestablish_tunnel - Set up a timer to try to reestablish the tunnel. This step is only performed
           if the tunnel was previously active and failed, and exist to provide a reconnection delay.

        1. start_tunnel - Run SSH to the Raspberry pi

        2. try_connect - Attempt to use the SSH connection to contact i3/Sway.
           If this fails, an automatic retry after a timeout is done.

        3. setup - Take over the i3/Sway setup

    """
    def __init__(self, parent=None):
        super().__init__(parent)

        self.reconnect_timer = QTimer()
        self.ssh_proc = QProcess()

        self.bus = QtDBus.QDBusConnection.sessionBus()
        self.dbus_adaptor = DBusAdaptor(self)
        self.contextual_executor = ContextualExecutor(self)

        if not self.bus.isConnected():
            raise Exception("Failed to connect to dbus!")

        self.bus.registerObject("/heimdall", self)
        self.bus.registerService("com.troshchinskiy.Heimdall")

        self.homedir = os.environ['HOME'] + "/.heimdall"
        self.read_config()
        self.start_tunnel()

    def echo(self, text):
        return text

    def version(self):
        return "0.1"

    def connect(self):
        self.ssh = Popen(["ssh"], stdout=PIPE)

    def read_config(self):
        filename = self.homedir + '/config.json'

        print("Loading config file {}...\n".format(filename))

        with open(filename, 'r') as conf_h:
            self.config = json.load(conf_h)

    def start_tunnel(self):
        if self.ssh_proc and self.ssh_proc.isOpen():
            print("Tunnel already running")
            return

        print("Starting tunnel...\n")

        sway_pid = self._run_remote(["pidof", "sway"])
        if sway_pid is None:
            raise Exception('Sway is not running!')

        home_dir = self._run_remote(["echo", '$HOME'])
        uid = self._run_remote(["echo", '$UID'])

        self.remote_socket = "/run/user/" + uid + "/sway-ipc." + uid + "." + sway_pid + ".sock"
        self.local_socket = self.homedir + "/sway.sock"

        print("Sway pid: '{}'".format(sway_pid))
        print("Home dir: '{}'".format(home_dir))
        print("UID     : '{}'".format(uid))
        print("Socket  : '{}'".format(self.remote_socket))

        if os.path.exists(self.local_socket):
            os.remove(self.local_socket)

        r = self.config['remote']

        command_args = [
            "-i", r['ssh-key'], "-p", r['port'], "-l", r['user'], "-R",
            r['backwards-port'] + ":127.0.0.1:" + r['local-ssh-port'], "-L",
            self.local_socket + ':' + self.remote_socket, r['server']
        ]

        print("Running command: ssh {}".format(command_args))

        self.ssh_proc.started.connect(self._ssh_process_started)
        self.ssh_proc.errorOccurred.connect(self._ssh_process_error)
        self.ssh_proc.finished.connect(self._ssh_process_finished)

        self.ssh_proc.start(self.config['commands']['ssh'], command_args)

    def try_connect(self):
        """Try to connect to i3/Sway.

        SSH takes a while to perform the port forwarding, so we may do this several times, until it starts
        working.
        """
        print("Trying to connect to Sway/i3 at socket {}...".format(
            self.local_socket))
        try:
            self.i3 = Connection(socket_path=self.local_socket)
        except ConnectionRefusedError:
            print("Not connected yet!")
            return
        except FileNotFoundError:
            print("Socket doesn't exist yet!!")
            return

        self.connect_timer.stop()
        self.setup()

    def setup(self):
        try:
            print("Setting up Sway/i3...")
            self.wm_version = self.i3.get_version()
            print("Connected to Sway/i3 version {}".format(self.wm_version))

            print("Resetting workspace...")
            for workspace in self.i3.get_workspaces():
                print("Deleting workspace {}".format(workspace.name))
                self.i3.command('[workspace="{}"] kill'.format(workspace.name))

            print("Executing commands...")
            for cmd in self.config['startup']['remote-run']:
                print("\tExecuting: {}".format(cmd))
                self._run_remote(cmd)

            print("Setting up workspaces...")
            wsnum = 0
            for wsconf in self.config['startup']['workspaces']:
                wsnum += 1
                self.i3.command("workspace {}".format(wsnum))
                self.i3.command('rename workspace "{}" to "{}"'.format(
                    wsnum, wsconf['name']))

                for wscmd in wsconf['commands']:
                    self.i3_command(wscmd)

        except (ConnectionRefusedError, FileNotFoundError):
            self._setup_reestablish_tunnel()

    def i3_command(self, command):

        command = command.replace('$TERM_EXEC_KEEP',
                                  self.config['remote']['terminal-exec-keep'])
        command = command.replace('$TERM_EXEC',
                                  self.config['remote']['terminal-exec'])
        command = command.replace('$TERM', self.config['remote']['terminal'])
        command = command.replace(
            '$SSH_TO_HOST', self.config['commands']['ssh'] + " -p " +
            self.config['remote']['backwards-port'] + " -t " +
            os.environ['USER'] + '@localhost ')

        print("Executing command: " + command)
        self.i3.command(command)

    def contextual_action(self, environment, path, command):
        self.contextual_executor.execute(environment, path, command)

    def stop_tunnel(self):
        """Stop the tunnel, if it's running"""

        if self.ssh_proc and self.ssh_proc.isOpen():
            print("Stopping ssh\n")
            self.ssh_proc.kill()
            self.ssh_proc.close()

        if os.path.exists(self.local_socket):
            os.remove(self.local_socket)

    def _setup_reestablish_tunnel(self):
        """Re-establish the SSH tunnel and begin again the process of syncing up"""

        self.stop_tunnel()
        self.reconnect_timer.timeout.connect(self.start_tunnel())
        self.reconnect_timer.singleShot(True)
        self.reconnect_timer.start(100)

    def _ssh_process_started(self):
        print("SSH process started!")
        self.connect_timer = QTimer()
        self.connect_timer.timeout.connect(self.try_connect)
        self.connect_timer.start(50)

    def _ssh_process_error(self, error):
        print("SSH process failed with error {}!".format(error))

    def _ssh_process_finished(self, exit_code, exit_status):
        print("SSH process exited with code {}, status {}!".format(
            exit_code, exit_status))

    def _run_remote(self, command):
        r = self.config['remote']

        ssh_command = [
            self.config['commands']['ssh'], "-i", r['ssh-key'], "-p",
            r['port'], "-l", r['user'], r['server']
        ]
        ssh_command += command

        print("Running: {}".format(ssh_command))
        result_raw = subprocess.run(ssh_command, stdout=subprocess.PIPE)
        result = result_raw.stdout.decode('utf-8').strip()
        return result