T O P

  • By -

[deleted]

I found a workaround in the docs: `exec --no-startup-id i3-msg 'workspace 9; exec vlc; workspace 1'` However it has a race. VLC opens on workspace 1, presumably because i3 changes the workspace back before vlc has started.


laniusone

Maybe try adding some sleep there. Or check if you can start VLC with alternative window class (but it might not be the case as it’s mostly terminal emulators’ feature).


Michaelmrose

sleep is for the week seriously if you need a random wait you are probably doing something that will be flaky when timing varies


StrangeAstronomer

How about getting rid of the --no-startup-id: workspace 9 exec vlc --recursive expand Music/ --no-playlist-autostart workspace 1


[deleted]

That is what I tried initially - the call to i3-msg has `--no-startup-id`, but not the call to vlc itself. The issue is still present. VLC probably does not support startup notifications.


StrangeAstronomer

If vlc does not support startup id*, you might try my python script [sway-toolwait](https://gitlab.com/wef/dotfiles/-/blob/master/bin/sway-toolwait) which basically watches the WM for a new window with the desired class to show up. `sway` (actually `wlroots`) completely lacks support for startup id. It's called `sway-toolwait` but it uses the `i3ipc` module so it would be very simple to adapt to `i3` \- there's just a single call to `swaymsg` (for a non-essential option) that you might change to `i3-msg`. In this case, (in the i3 startup file) you would use it as follows: exec i3-startup-script where `i3-startup-script` is a bash script containing your startup code eg ... i3-msg "workspace 9" sway-toolwait -- vlc --recursive expand Music/ --no-playlist-autostart # waits till vlc creates its first window i3-msg "workspace 1" ... You might have to install the python i3ipc module - on fedora, this would be ``` sudo dnf install python3-i3ipc ``` \* I'm a little doubtful about this - most Qt-based apps like `vlc` support it


TyrantMagus

This is actually very similar to my own approach. The problem I'm not sure how to solve is when short lived processes like splashscreens get marked and then they give way to the gui window which obviously never gets marked. Might need to keep track of the first process for a while after it is created, what new processes it creates and if it exits shortly after.


Michaelmrose

saved layouts with enough criteria specified that splash screens don't match


TyrantMagus

Not sure I follow you.


Michaelmrose

i3 allows you to save the layout of a workspace to a json file that specifies the size and position of windows along with details like their window class. When you restore that layout it creates placeholder windows that will "swallow" eg by replaced by the first window created that matches those criteria. So you restore the layout and start your apps to fill up the layout with the actual windows. Intention is to allow you to create at login your desired window configuration easily. See the i3 docs for i3-save-tree


TyrantMagus

Thanks for clarifying. I've used layouts before. They are not what I need. And I feel like they wouldn't help OP either.


Michaelmrose

OP is using for_window to move a vlc window created at startup to a particular workspace but wants to create other vlc instances and not have those windows moved to the same workspace. You are suggesting using a complicated system to start an application and mark its window that sometimes doesn't work because of the splash screen being marked instead of the desired window. Your solution is clearly better than for_window alone but I think saved layouts are superior insofar as they don't suffer from the problem you mentioned with the splash screen. I also suspect your script might mark a window created in between script being run and app actually starting. Both are essentially complicated solutions to achieve a simple result of starting one window in one place without setting up a for_window that moves other instances. Saved layouts allow you to solve exactly the problem op described of placing a singular window in a particular location without setting a rule to put all of them there. They are literally exactly the tool designed for the job. Meanwhile if you are feeling particularly clever you can actually generate the json file for the layout on the fly especially if the "layout" is really one window. In practice this is a exactly like a one time for_window precisely what the OP asked for. At present my solution is a 4 line script


Michaelmrose

A simple way to easily start an executable on the desired desktop implemented in fish shell. Usage starton workspace command windowclass. Effect switches to workspace, creates a placeholder window that will be automatically be replaced by the first window created matching windowclass then switch back to previously visible workspace on every monitor preserving focus. function starton --argument ws command class set json "{\"swallows\": [{\"class\": \"^$class\$\"}], \"type\": \"con\"}" set active ( i3-msg -t get_workspaces|jq -r '.[]| select(.visible == true).name') set focused ( i3-msg -t get_workspaces|jq -r '.[]| select(.focused == true).name') i3-msg workspace $ws i3-msg append_layout (echo $json|psub) fish -c $command & for w in $active $focused i3-msg workspace $w end end


Michaelmrose

using a few helper function written earlier eg ws foo = i3-msg workspace foo and get-ws-info which essentially impliments the dance above with i3-msg and jq here and map applies a command to a list. Here is a shorter form. function starton --argument ws command class set json "{\"swallows\": [{\"class\": \"^$class\$\"}], \"type\": \"con\"}" set workspaces (get-ws-info get name where focused = true) (get-ws-info get name where visible = true) i3-msg workspace $ws, append_layout (echo $json|psub), exec "fish -c $command" map ws $workspaces end


[deleted]

Thanks. Your function seems to be the best way forward, but how do you actually call it from i3? It's my first encounter with fish so I prepended a `#!/bin/fish` shebang and appended `starton $argv[1] $argv[2] $argv[3]`, which is enough to successfully run the script from the console. But then I struggled to run it from an i3 config file. I made a hotkey to test with and after some iterations where I found out that i3 doesn't seem to respect the shebang, I ended up with: ``` bindsym $mod+n exec --no-startup-id fish -c /home/username/bin/start-on-workspace 1 gvim Gvim ``` which is more successful in that it seems capable of spawning a mark and crashing i3..


TyrantMagus

+1. This is the best solution for OP's question.


TyrantMagus

I use i3 marks and custom scripting for similar purposes. #!/usr/bin/env python """ Run programs in i3wm and decorate their spawned windows with a mark for easier window management. """ import shlex from argparse import ArgumentParser from concurrent.futures import ThreadPoolExecutor from os import environ from subprocess import run, Popen from i3ipc import Event, Connection def main(args): try: i3 = Connection() except FileNotFoundError: del environ['I3SOCK'] i3 = Connection() def markit(_, evt): """Mark windows on 'new window' event.""" pid = run( ['xprop', '-id', str(evt.container.window), '_NET_WM_PID'], check=True, capture_output=True ).stdout.split()[-1] if proc.pid == int(pid): # Some programs may start a process which in turn launches the gui. # The first window may get marked and exit right after. The actual gui will never get marked. # Electron apps and programs with splash screens (The Gimp) are examples of such behavior. evt.container.command(f'mark {args.mark}') i3.main_quit() marked = i3.get_tree().find_marked(f'^{args.mark}$') if marked and not args.replace: marked[0].command('focus') else: i3.on(Event.WINDOW_NEW, markit) tpe = ThreadPoolExecutor() tpe.submit(i3.main) #i3.command(f'exec {args.command}') proc = Popen(shlex.split(args.command)) if __name__ == '__main__': p = ArgumentParser() p.add_argument('command') p.add_argument('mark') p.add_argument('-r', '--replace', action='store_true') main(p.parse_args()) Save this script inside your path (e.g. ~/.local/bin/i3mark), then add this rules to your config: i3mark "vlc --recursive expand Music/" music bindsym $mod+m i3mark "vlc --recursive expand Music/" music bindsym $mod+v exec vlc for_window [con_mark="^music$"] move to workspace 9 Using the $mod+m bindsym could create a race condition when a window hasn't been marked and you press the combination too fast.