#!/usr/bin/perl use strict; use warnings; =pod =head1 DESCRIPTION I is a launcher for desktop applications. It creates application-specific screen pages; starts applications; and initializes them. Deprecated, in favour of scripts like /home/mike/system/bin/lay_*, that use Mike::Lay. =head1 SYNOPSIS /usr/local/bin/desk-launch [I] I /usr/local/bin/desk-launch --help | --man =cut use lib "$ENV{HOME}/.config", '/etc/xdg'; # for desktop config files use lib '/home/mike/code/textbender/x/repo'; use File::Basename qw( basename ); use Getopt::Long (); use Pod::Usage (); use Zelea::WindowManagement qw( newest_window ); sub _change_window_state( $$$;$ ); # cf. screenpager # ( window, add|remove|toggle, property name ; property name 2 ) # # Deprecated in favour of in-line call. # # Adds, removes or toggles the specified _NET_WM_STATE property (or two) # for the specified window. # The property name is in wmctrl -b format; one of: # modal, sticky, maximized_vert (but see below), maximized_horz, shaded, # skip_taskbar, skip_pager, hidden, fullscreen, above, below. # # This is not very useful for vertical maximization. # It doesn't play well with my other controls, # because I cannot undecorate the window at the same time, as they do; # the corresponding state atom is specific to the WM, # and unsupported by wmctrl. # # Altering the uncedorate atom via X11::Protocol might work, # later, when I feel like playing with it again. # # I tried doing the undecorate within the same Openbox menu item # that launches the application. # That required a new option, --activate: # "Activates the new application window. # Used when launching from Openbox, # so that Openbox can undecorate the window afterwards. # If you do not specify this option, # then the window will not be vertically maximized # (in which case you can both # maximize and undecorate using 0 ); # and exits } } defined $_screen and return $_screen; my $command = "screenpager$screen_option_relay output screen"; $_screen = `$command`; # e.g. 1 $_screen =~ m'^[0-9]+$' or die; chop $_screen; return $_screen; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _get_screen_x() { require screenpager::Config_1; my $x = 0; # thus far my $screen = _get_screen; my $s = 0; for(; $s < $screen; ++$s ) { my $s_config_HASH = $screenpager::Config_1::screen[$s]; $x += $$s_config_HASH{width}; } return $x; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _get_screen_x_right() { require screenpager::Config_1; my $x = 0; # thus far my $screen = _get_screen; my $s = scalar @screenpager::Config_1::screen - 1; for(; $s > $screen; --$s ) { my $s_config_HASH = $screenpager::Config_1::screen[$s]; $x += $$s_config_HASH{width}; } return $x; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _get_wininfo( $ ) { my $window = shift; my( $title, $height ); my( $frame_left, $frame_right, $frame_top, $frame_bottom ) = _get_frame_strut( $window ); my $command = "xwininfo -id $window -stats"; my $wininfo = `$command`; # e.g. ~20 lines of data including: # xwininfo: Window id: 0x100001d "Xfe" # Height: 619 { $wininfo =~ m'^ *xwininfo:.*"(.*)"$'m or die; $title = $1; } { $wininfo =~ m'^ *Height: *([0-9]+)$'m or die; my $nominal_height = $1; $height = $nominal_height + $frame_top + $frame_bottom; } return ( $title, $height ); } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _get_wm_state( $ ) { my $window = shift; my( $is_decorated, $is_fullscreen, $is_maximized_horz, $is_maximized_vert ); my $command = "xprop -id $window _NET_WM_STATE"; $_ = `$command`; # e.g. _NET_WM_STATE(ATOM) = _NET_WM_STATE_FULLSCREEN, _NET_WM_STATE_MAXIMIZED_VERT, _OB_WM_STATE_UNDECORATED $is_decorated = $_ !~ m'\b_OB_WM_STATE_UNDECORATED\b'; # only works for Openbox $is_fullscreen = m'\b_NET_WM_STATE_FULLSCREEN\b'; $is_maximized_horz = m'\b_NET_WM_STATE_MAXIMIZED_HORZ\b'; $is_maximized_vert = m'\b_NET_WM_STATE_MAXIMIZED_VERT\b'; return ( $is_decorated, $is_fullscreen, $is_maximized_horz, $is_maximized_vert ); } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _launch__and_exit( \& ) { my $launch_SUB = shift; my $scale = _get_scale; if( $scale eq 'page' ) { _create_page; } else { $scale eq 'instance' or Pod::Usage::pod2usage( -verbose => 0 ); # and exits } &$launch_SUB; _display_if_requested_and_exit; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _launch_editor( @ ) { my $command; my $expected_title_prefix; my $window; if( defined $option{'side-kick'} ) # deprecated { $command = 'emacs --funcall=_load-editable-tasks_init &'; my $last_file_to_load = '/home/mike/.task'; # per .emacs _load-editable-tasks_init() system $command and die 'unable to execute: ' . $command; my $start_filename; { $last_file_to_load =~ m|/([^/]+)$| or die; $start_filename = $1; } $expected_title_prefix = $start_filename . ' '; $window = newest_window( 2, "^$expected_title_prefix" ); } else # file edit { my $filespec; if( scalar @_ ) { my $last_file_to_load = $_[$#_]; $expected_title_prefix = quotemeta( basename( $last_file_to_load )); # $filespec = ' ' . join( ' ', @_ ); for my $f ( @_ ) { $filespec .= " '$f'" } } else { $expected_title_prefix = '\*scratch\*'; $filespec = ''; } $command = "emacs$filespec &"; system $command and die 'unable to execute: ' . $command; $window = newest_window( 1, "^$expected_title_prefix" ); # wait for it to appear, even if not moving it, else user's subsequent calls to cascade-pilot etc. will grab wrong window } if( defined $option{'side-kick'} || defined $option{'screen'} ) # then move it { my $screen = _get_screen; require Zelea::Desktop; if( $screen != $Zelea::Desktop::editor_screen ) # then must move window to $screen { # my $x = 1375; # per $Zelea::Desktop::editor_screen EDITOR_SCREEN my $x = 2655; # per $Zelea::Desktop::editor_screen EDITOR_SCREEN require screenpager::Config_1; if( $screen < $Zelea::Desktop::editor_screen ) { for( my $s = $Zelea::Desktop::editor_screen; $s > $screen; --$s ) { my $s_config_HASH = $screenpager::Config_1::screen[$s]; $x -= $$s_config_HASH{width}; } } else { for( my $s = $screen; $s > $Zelea::Desktop::editor_screen; --$s ) { my $s_config_HASH = $screenpager::Config_1::screen[$s]; $x += $$s_config_HASH{width}; } } # $screen == $Zelea::Desktop::monitor_screen and $x -= 80; _reposition_window( $window, $x ); } } } sub _launch_firefox() { use textbender::a::b::Function qw( join_clean system_or_print ); use Zelea::Desktop qw( $MONITOR_SCREEN ); # my $profile = $option{profile}; $profile or $profile = 'default'; ### but can only open 1 window, if specifying profile, so do not specify for default: my $profile_spec = ''; # till proven otherwise $option{profile} and $profile_spec = ' -P ' . $option{profile}; my $command = join_clean( ' ', '/usr/bin/firefox', $profile_spec, @ARGV, '&' ); system $command and die 'unable to execute: ' . $command; my $screen = _get_screen; my $window = newest_window 1, 'Mozilla Firefox$'; # should be 3, but I'm impatient _demax( $window ); # Firefox opens maximized if previous instances are/were maximized my( $x, $width, $height ); { require screenpager::Config_1; my $screen_config_HASH = $screenpager::Config_1::screen[$screen]; my( $frame_left, $frame_right, $frame_top, $frame_bottom ) = _get_frame_strut( $window ); use integer; my $left_margin = $$screen_config_HASH{width} / 4 - 50; my $right_margin = 60; $x = _get_screen_x + $left_margin; $width = $$screen_config_HASH{width} - $left_margin - $frame_left - $right_margin - $frame_right; $height = 7 * $$screen_config_HASH{height} / 8 - $frame_top - $frame_bottom; } _reposition_window( $window, $x, 0, $width, $height ); if( $screen != $MONITOR_SCREEN ) { system_or_print "wmctrl-zelea -i -r $window -b add,_OB_WM_STATE_UNDECORATED,maximized_vert" and die; $width < 760 and system_or_print "wmctrl-zelea -i -r $window -b add,maximized_horz" and die; } } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _launch_galeon( ;$$ ) { my $URI = shift; my $title_pattern = shift; require Zelea::GaleonService; my $command; if( !Zelea::GaleonService::is_running() ) # means *maybe* not running { $command = '/usr/local/bin/galeon-service start'; # no harm if already running system $command and die 'unable to execute: ' . $command; sleep 3; } my $load_directly = defined $URI && defined $title_pattern; $command = 'galeon --noraise --new_window'; $load_directly and $command .= " $URI"; system $command and die 'unable to execute: ' . $command; # will block here if service not really running, in which case, before trying again, you will need to kill this process, and then call /usr/local/bin/galeon-service clean my $window; if( $load_directly ) { $window = newest_window( 2, $title_pattern ); } else { $window = newest_window( 1, '^blank black$' ); } _demax( $window ); # Galeon opens maximized if previous instances are/were maximized my( $x, $width, $height ); { require screenpager::Config_1; my $screen = _get_screen; my $screen_config_HASH = $screenpager::Config_1::screen[$screen]; my( $frame_left, $frame_right, $frame_top, $frame_bottom ) = _get_frame_strut( $window ); use integer; my $left_margin = $$screen_config_HASH{width} / 4 - 50; my $right_margin = 60; $x = _get_screen_x + $left_margin; $width = $$screen_config_HASH{width} - $left_margin - $frame_left - $right_margin - $frame_right; $height = 7 * $$screen_config_HASH{height} / 8 - $frame_top - $frame_bottom; } _reposition_window( $window, $x, 0, $width, $height ); # $width < 700 and _change_window_state( $window, 'add', 'maximized_horz' ); # maximized_vert too, but per _change_window_state ## put elsewhere if you want it, because it makes the sub less predictable for clients who want to manipulate window after if( !$load_directly && defined $URI ) { # sleep 1; #### no help, if a previous browser is visible, it takes the URI: $command = 'galeon --existing ' . $URI; system $command and die 'unable to execute: ' . $command; } return $window; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _launch_javadoc_browser() { use Zelea::Desktop (); use Zelea::Java (); my $screen = _get_screen; my $screen_x = _get_screen_x; # Index browser. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - my $window = _launch_galeon ( Zelea::Java::get_JDK_dir() . '/docs/api/allclasses-noframe.html', '^All Classes' ); my( $x, $width ); $x = $screen_x; $screen == $Zelea::Desktop::monitor_screen and $x += 100; # room for monitor $width = 368; # the minimum it will shrink to _reposition_window( $window, $x, -1, $width ); # Class browser. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - my( $frame_left, $frame_right, $frame_top, $frame_bottom ) = _get_frame_strut( $window ); $x += $width + $frame_left + $frame_right; # to right edge of the index browser my $screen_config_HASH = $screenpager::Config_1::screen[$screen]; $width = $screen_x + $$screen_config_HASH{width} - $x - $frame_left - $frame_right; $screen == $Zelea::Desktop::monitor_screen and $width -= 70; # room for monitor $window = _launch_galeon; _reposition_window( $window, $x, -1, $width ); } sub _launch_mozilla() { my $profile_spec = ''; # till proven otherwise $option{profile} and $profile_spec = ' -P ' . $option{profile}; my $command = "/usr/lib/mozilla/mozilla-bin$profile_spec &"; system $command and die 'unable to execute: ' . $command; my $window = newest_window 3, '^.*Mozilla$'; _demax( $window ); # Mozilla opens maximized if previous instances are/were maximized my( $x, $width, $height ); { require screenpager::Config_1; my $screen = _get_screen; my $screen_config_HASH = $screenpager::Config_1::screen[$screen]; my( $frame_left, $frame_right, $frame_top, $frame_bottom ) = _get_frame_strut( $window ); use integer; my $left_margin = $$screen_config_HASH{width} / 4 - 50; my $right_margin = 60; $x = _get_screen_x + $left_margin; $width = $$screen_config_HASH{width} - $left_margin - $frame_left - $right_margin - $frame_right; $height = 7 * $$screen_config_HASH{height} / 8 - $frame_top - $frame_bottom; } _reposition_window( $window, $x, 0, $width, $height ); $width < 700 and _change_window_state( $window, 'add', 'maximized_horz' ); # maximized_vert too, but per _change_window_state } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _launch_pdf_viewer( ;$ ) { my $file = shift; defined $file or $file = ''; my $command = "/usr/local/Acrobat5/bin/acroread $file &"; system $command and die 'unable to execute: ' . $command; my $title_pattern; if( $file ) { my $filename; { $file =~ m|/([^/]+)$| or die; $filename = $1; } $title_pattern = "^$filename\$"; } else { $title_pattern = '^Acrobat Reader$'; } my $window = newest_window( 3, $title_pattern ); my( $x, $y, $width, $height ); { require screenpager::Config_1; my $screen = _get_screen; my $screen_config_HASH = $screenpager::Config_1::screen[$screen]; my( $frame_left, $frame_right, $frame_top, $frame_bottom ) = _get_frame_strut( $window ); use integer; my $left_margin = $$screen_config_HASH{width} / 8; $x = _get_screen_x + $left_margin; $y = 0; $width = $left_margin * 6; $height = 7 * $$screen_config_HASH{height} / 8 - $frame_top - $frame_bottom; } _reposition_window( $window, $x, $y, $width, $height ); } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _launch_reference_browser( $;$ ) { my $URI = shift; my $units_offset = shift; defined $units_offset or $units_offset = 0; require Zelea::GaleonService; my $command; if( !Zelea::GaleonService::is_running() ) # means *maybe* not running { $command = '/usr/local/bin/galeon-service start'; # no harm if already running system $command and die 'unable to execute: ' . $command; sleep 3; } $command = "galeon --new_window $URI"; system $command and die 'unable to execute: ' . $command; # will block here if service not really running, in which case, before trying again, you will need to kill this process, and then call /usr/local/bin/galeon-service clean require screenpager::Config_1; my $screen = _get_screen; my $screen_config_HASH = $screenpager::Config_1::screen[$screen]; my $window = newest_window( 1, "^$URI\$" ); _demax( $window ); # Galeon opens maximized if previous instances are/were maximized my( $x, $y, $width, $height ); { my( $frame_left, $frame_right, $frame_top, $frame_bottom ) = _get_frame_strut( $window ); if( defined $option{'side-kick'} ) { $units_offset == 0 or die; $x = _get_screen_x; my $top_margin = 203; # height of Galeon's Find window $y = $top_margin; $$screen_config_HASH{height} > 1000 and $y += $frame_top; # workaround for wmctrl bug $height = $$screen_config_HASH{height} - $top_margin - $frame_top - $frame_bottom; use integer; $width = $$screen_config_HASH{width} / 2 - $frame_left - $frame_right; } else { use integer; my $left_margin = $$screen_config_HASH{width} / 6; $x = _get_screen_x + $left_margin + $units_offset * $frame_top; $y = abs($units_offset) * $frame_top; $width = $$screen_config_HASH{width} - $left_margin - $frame_left - $frame_right; $height = 3 * $$screen_config_HASH{height} / 4 - $frame_top - $frame_bottom; require Zelea::Desktop; if( $screen == $Zelea::Desktop::monitor_screen ) # then let some more background show: { $width -= 60; } } } _reposition_window( $window, $x, $y, $width, $height ); $$screen_config_HASH{width} < 1100 && !defined $option{'side-kick'} and _change_window_state( $window, 'add', 'maximized_horz' ); # maximized_vert too, but per _change_window_state } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sub _corrected_wmctrl_position_value( $ ); sub _reposition_window( $$;$$$ ) { my $window = shift; my $x = _corrected_wmctrl_position_value shift; my $y = _corrected_wmctrl_position_value shift; my $width = _corrected_wmctrl_position_value shift; my $height = _corrected_wmctrl_position_value shift; my $command = "wmctrl -i -r $window -e 0,$x,$y,$width,$height"; # gravity,x,y,width,height system $command and die 'unable to execute: ' . $command; } sub _corrected_wmctrl_position_value( $ ) { my $value = shift; if( defined $value ) { $value == -1 and $value = 0; } else { $value = -1; # wmctrl 'to leave the property unchanged' } return $value; } ##### A r g u m e n t s ################################################################## =pod =head1 ARGUMENTS =over 8 =cut _shift_options; my $argument = shift; defined $argument or $argument = ''; # prevent warnings # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if( $argument eq 'editor' ) { =pod =item B [I] | --side-kick B Specifies an editor; either a typical editor, or a side-kick with several standard files in it. =cut my $scale = _get_scale; if( $scale eq 'page' ) { _create_page; } else { $scale eq 'instance' or Pod::Usage::pod2usage( -verbose => 0 ); # and exits } _launch_editor( @ARGV ); _display_if_requested_and_exit; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if( $argument eq 'javadoc-browser' ) { =pod =item B Specifies a web browser with a side-kick browser for navigation. This is meant for viewing javadocs, in lieu of using frames with Galeon. A Galeon quirk prevents it marking links as visited when the target is in another frame. This makes it difficult to navigate the javadoc indeces. So use this side-kick arrangement instead, and drag your links into the main browser window. When you refresh the side-kick, it will show the visited links. =cut my $scale = _get_scale; if( $scale eq 'page' ) { _create_page; } else { $scale eq 'instance' or Pod::Usage::pod2usage( -verbose => 0 ); # and exits } _launch_javadoc_browser; _display_if_requested_and_exit; } # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # if( $argument eq 'mail' ) # { #=pod # #=item B # #Specifies a mail client. # #=cut # # _launch__and_exit( &_launch_mail ); # } # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if( $argument eq 'pdf-viewer' ) { =pod =item B [I] Specifies a PDF viewer. You can optionally name the PDF file to view. =cut my $file = shift; my $scale = _get_scale; if( $scale eq 'page' ) { _create_page; } else { $scale eq 'instance' or Pod::Usage::pod2usage( -verbose => 0 ); # and exits } _launch_pdf_viewer $file; _display_if_requested_and_exit; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if( $argument eq 'web-browser' ) { =pod =item B Specifies a web browser. you can optionally provide a profile name using --profile. You can only launch a single browser per profile, or Mozilla complains. =cut _launch__and_exit( &_launch_mozilla ); } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if( $argument eq 'web-browser-firefox' ) { =pod =item B [-- I] Specifies a Firefox web browser. A page URI may be included in arguments-for-browser. Firefox 'Tabs Preferences' must be set to: 'open links from other applications in a new window,' or the page will appear in a prior browser window, if any exists (even if you specify '-browser' to request a new window). =cut _launch__and_exit( &_launch_firefox ); } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if( $argument eq 'web-browser-text' ) { =pod =item B [I] Specifies a text-only web browser. You can optionally provide an initial URI. =cut my $URI = shift; my $scale = _get_scale; if( $scale eq 'page' ) { _create_page; } else { $scale eq 'instance' or Pod::Usage::pod2usage( -verbose => 0 ); # and exits } _launch_galeon $URI; _display_if_requested_and_exit; } =pod =back =cut Pod::Usage::pod2usage( -verbose => 0 ); # and exits ##### O p t i o n s ###################################################################### sub _shift_options() { my @specification; =pod =head1 OPTIONS =over 8 =cut push @specification, 'display'; =pod =item --B Turns screenpager's display on, before exiting. =cut push @specification, 'move-down'; =pod =item --B Moves the page down after launching. =cut push @specification, 'help|?'; =pod =item --B Outputs a brief help message and exits. =cut push @specification, 'man'; =pod =item --B Outputs the full manual page and exits. =cut push @specification, 'profile:s'; =pod =item --B=I Specifies the mozilla profile to use, per C =cut # push @specification, 'scale:s'; push @specification, 'scale=s'; =pod =item --B=instance|page Specifies the scale of launch: C to launch a single instance of the application (this is the default); or C to create a full screen page, and populate it with one or more instances. =cut push @specification, 'screen=s'; =pod =item --B=I|mouse Specifies the screen to act on, either by number; or by mouse position. The default is the current focus of C. =cut push @specification, 'side-kick'; =pod =item --B Specifies a side-kick orientation of the launched window. =cut Getopt::Long::GetOptions( \%option, @specification ) or Pod::Usage::pod2usage( -verbose => 0 ); # and exits =pod =back =cut Pod::Usage::pod2usage( -verbose => 1 ) if defined $option{'help'}; # and exits Pod::Usage::pod2usage( -verbose => 2 ) if defined $option{'man'}; # and exits } __END__ =pod =head1 AUTHOR Michael Allan =cut