#!/usr/bin/perl

# ~ ~ ~ Open In Desktop ~ ~ ~

###
# INSTRUCTIONS

# To open pcmanfm in the 3rd desktop (wmctrl calls it desktop 2):
# $ perl oid.pl 3 pcmanfm

# To open iceweasel in the 1st desktop (wmctrl calls it desktop 0),
# with a timeout (maximum waiting time for the window to open) of 5 seconds:
# $ perl oid.pl 1 -t 5 iceweasel www.linux.org

# PS: you probably want to create this alias in your .bashrc file:
#alias oid="perl oid.pl"

use strict;
use warnings;

###
## FUNCTIONS => check out the script first, at the end of this file

# Arguments: command line arguments
# Returns: desktop number, timeout duration (in seconds), 
#          command to execute (generally a program to start)
sub retrieve_args {
    my @cmd_line_args = @{shift()};

    # The first argument HAS to be a desktop number!
    my $desk_num = shift @cmd_line_args;
    die 'The first argument (currently $desk_num) must be a desktop number' 
        if $desk_num !~ m{(\d)+}xms; 

    # Did the user specify a timeout?
    my $timeout = 1;    # Default timeout 
    if ($cmd_line_args[0] eq '-t') {
        # User-specified timeout
        shift @cmd_line_args;              # get rid of the '-t' argument
        $timeout = shift @cmd_line_args;   # retrieve the requested timeout
        
        # Sanity checks
        die 'The timeout must be a number' 
            if $timeout !~ m{(\d)+}xms; 
        my $MAX_ADVISED_TIMEOUT = 10;
        warn "The timeout probably should not be that big: $timeout, really??"
            if $timeout > $MAX_ADVISED_TIMEOUT;
    }

    # Retrieve the command to execute
    my $COMMAND = join(' ', @cmd_line_args);

    my @args = ($desk_num, $timeout, $COMMAND);

    return \@args;
}   # end function retrieve_args()

###
# Argument: list of windows that were previously opened 
# Returns a list of newly opened windows
sub wait_until_a_new_window_is_opened {
    my @already_opened_windows = @{shift()};
    my $timeout = shift;

    # Wait (or timeout)
    my $time_orig = time;
    my @opened_windows = `wmctrl -l`;
    until ( @opened_windows > @already_opened_windows ) {
            
        @opened_windows = `wmctrl -l`;

        # Notify if no window was opened at timeout
        die "No new window has been opened since $timeout seconds."
            . " You can change the timeout with the -t argument:\n"
            . "oid COMMAND -t 3 (for example)" 
            if time - $time_orig > $timeout;
    }

    # Identify the window(s) that have been added
    my @new_windows = ();
    foreach my $window ( @opened_windows ) {
        # This window was already opened before
        next if grep { $window eq $_ } @already_opened_windows;

        # Add the new window to the list of newly opened windows
        push @new_windows, $window;
    }

    return \@new_windows;
}   # end function wait_until_a_new_window_is_opened()

###
# Arguments: list of windows to move
sub move_windows_to_desktop_num {
    my @windows_to_move = @{shift()};
    my $desk_num    = shift;

    foreach my $window (@windows_to_move) {
    
        # Retrieve the window name alone, from the wmctrl line
        my $CLIENT_MACHINE_NAME = `uname -n`;
        my ($window_name) = ( 
            $window =~ m{$CLIENT_MACHINE_NAME [ ] (.*)}xms
            );
        chomp $window_name;  # get rid of the newline

        # Create the desktop if it doesn't exist
        `wmctrl -n $desk_num`;  
            # Surprisingly, this command creates desktop $desk_num, not
            # $desk_num-1

        # Move the window
        my $wmctrl_desk_num = $desk_num - 1;
        `wmctrl -r "$window_name" -t $wmctrl_desk_num`;
            # desktop $wmctrl_desk_num in this -t command, is the same as desktop 
            # $desk_num in the previous -n command. Surprisingly...

    }   # end foreach ($window)

    # Tell the user what happened
    my $plural = @windows_to_move > 1 ? 's' : '';
    print "oiw has opened @windows_to_move window$plural in desktop $desk_num"

}   # end function move_windows_to_desktop_num()


###
# SCRIPT

###
# 1. Retrieve command line args
my ($desk_num, $timeout, $COMMAND) = @{retrieve_args( \@ARGV )};

###
# 2. Do the job 

# List the windows that were opened before the command execution
my @already_opened_windows = `wmctrl -l`;

# execute command, adding ' &' in the end, and wait until the window is opened
system("$COMMAND &");

# Wait until there more windows opened than there used to be
my @new_windows = @{wait_until_a_new_window_is_opened( 
    \@already_opened_windows,
    $timeout,
    )};

# Move each window to the requested desktop
move_windows_to_desktop_num( \@new_windows, $desk_num );
 

