# -*- perl -*- # # $Id: Win32Util.pm,v 1.35 2005/07/02 22:32:21 eserte Exp eserte $ # Author: Slaven Rezic # # Copyright (C) 1999-2004 Slaven Rezic. All rights reserved. # This package is free software; you can redistribute it and/or # modify it under the same terms as Perl itself. # # Mail: slaven@rezic.de # WWW: http://www.rezic.de/eserte/ # package Win32Util; =head1 NAME Win32Util - a collection of Win32 related functions =head1 SYNOPSIS use Win32Util; =head1 DESCRIPTION This is a collection of Win32 related functions. There are no strict prerequirements for this module, however, full functionality can only be achieved if some CPAN modules (Win32::Registry, Win32::API, Win32::DDE, Win32::Shortcut ...) are available. By default, most of these modules are already bundled with the popular ActivePerl package. =cut use strict; use vars qw($DEBUG $browser_ole_obj $VERSION); $VERSION = sprintf("%d.%02d", q$Revision: 1.35 $ =~ /(\d+)\.(\d+)/); $DEBUG=0 unless defined $DEBUG; # XXX Win-Registry-Funktionen mit Hilfe von Win32::API und # der Hilfe von der Access-Webpage nachbilden... # Laut Microsoft-Dokumentation soll für den Ort des Programm-Verzeichnisses # die Funktion # SHGetSpecialFolderLocation(..., CSIDL_PROGRAMS, ...) # verwendet werden. use vars qw(%API_FUNC %API_DEF); %API_DEF = ("SystemParametersInfo" => {Lib => "user32", In => ['N', 'N', 'P', 'N'], Out => 'N'}, "GetSystemMetrics" => {Lib => "user32", In => ['N'], Out => 'N'}, "SHAddToRecentDocs" => {Lib => "shell32", In => ['I', 'P'], Out => 'I'}, "SHGetSpecialFolderLocation" => {Lib => "shell32", In => ['I', 'I', 'P'], Out => 'I'}, "SHGetPathFromIDList" => {Lib => "shell32", In => ['P', 'P'], Out => 'I'}, "GetLogicalDrives" => {Lib => "kernel32", In => [], Out => "I"}, "GetDriveType" => {Lib => "kernel32", In => ["P"], Out => "I"}, "GetSysColor" => {Lib => "user32", In => ['N'], Out => 'N'}, "GetUserName" => {Lib => "advapi32", In => ['P', 'P'], Out => 'I'}, "ShowWindow" => {Lib => "user32", In => ['I', 'I'], Out => "I"}, "DrawMenuBar" => {Lib => "user32", In => ['N'], Out => 'N'}, "GetMenuItemCount" => {Lib => 'user32', In => ['N'], Out => 'N'}, "GetMenuItemID" => {Lib => 'user32', In => ['N','N'], Out => 'N'}, "GetSystemMenu" => {Lib => 'user32', In => ['N','N'], Out => 'N'}, "RemoveMenu" => {Lib => 'user32', In => ['N','N','N'], Out => 'N'}, "SetWindowPos" => {Lib => 'user32', In => [qw(N N N N N N N)], Out => 'N'}, ); sub _get_api_function { my $name = shift; eval { require Win32::API; if (!exists $API_FUNC{$name}) { my $def = $API_DEF{$name}; if (!$def) { die "No API definition for $name"; } $API_FUNC{$name} = new Win32::API ($def->{Lib}, $name, $def->{In}, $def->{Out}); } }; warn $@ if $@; $API_FUNC{$name}; } =head1 PROGRAM EXECUTION FUNCTIONS =head2 start_any_viewer($file) Based on extension of the given $file, start the appropriate viewer. =cut sub start_any_viewer { my $file = shift; require File::Basename; my($n,$p,$suffix) = File::Basename::fileparse($file, "\.[^.^]*"); if ($suffix =~ /^html?$/) { return start_html_viewer($file); } elsif ($suffix eq 'ps') { return start_ps_viewer($file); } else { my $class = get_class_by_ext($suffix); if ($class) { my $cmd = get_reg_cmd($class); if (!$cmd) { warn "No command for class $class"; } else { return start_cmd($cmd, $file); } } else { warn "Can't start viewer for $file"; } } 0; } =head2 start_html_viewer($file) Start a html viewer with the given file. This is mostly a WWW browser. =cut sub start_html_viewer { my $file = shift; if (!start_html_viewer_cmd($file)) { if (!start_html_viewer_dde($file)) { system("netscape $file &"); if ($?) { return undef } } } 1; } sub start_html_viewer_cmd { my $file = shift; my $html_viewer = get_html_viewer(); if ($html_viewer =~ /netscape/i) { # Bei Netscape: HTML-Viewer funktioniert auf Dateien, nicht auf URLs! $file =~ s/^file://; } start_cmd($html_viewer, $file); } =head2 start_ps_viewer($file) Start a postscript viewer with the given file. =cut sub start_ps_viewer { my $file = shift; if (!start_ps_viewer_cmd($file)) { system("gsview32 $file %"); if ($?) { return undef } } 1; } sub start_ps_viewer_cmd { my $file = shift; my $ps_viewer = get_ps_viewer(); start_cmd($ps_viewer, $file); } sub ps_viewer_available { my $ps_viewer = get_ps_viewer(); return 1 if defined $ps_viewer && $ps_viewer ne ""; return 1 if is_in_path("gsview32"); return 0; } =head2 start_ps_print($file) Print a postscript file via a postscript viewer. =cut sub start_ps_print { my $file = shift; if (!start_ps_print_cmd($file)) { system("gsview32 /p $file %"); if ($?) { return undef } } 1; } sub start_ps_print_cmd { my $file = shift; my $ps_print = get_ps_print(); start_cmd($ps_print, $file); } # XXX Maybe make a start_pdf_print with this information: # # --- CUT START --- # Other options for the command line are: # AcroRd32.exe /p filename - Executes the Reader and prints a file. # /n Launch a separate instance of the Acrobat application, even if one is # currently open. # /s Open Acrobat, suppressing the splash screen. # /o Open Acrobat, suppressing the open file dialog. # /h Open Acrobat in hidden mode. # AcroRd32.exe /t path printername drivername portname - Initiates # Acrobat Reader, prints a file while suppressing the Acrobat print dialog # box, then terminates Reader. # The four parameters of the /t option evaluate to path, printername, # drivername, and portname (all strings). # printername - The name of your printer. # drivername - Your printer driver's name. Whatever appears in the Driver Used # box when you view your printer's properties. # portname - The printer's port. portname cannot contain any "/" characters; # if it does, output is routed to the default port for that printer. # --- CUT END --- sub start_txt_print { my $file = shift; my $txt_print = get_txt_print(); start_cmd($txt_print, $file); } sub start_html_viewer_dde { my $file = shift; my ($app, $topic) = get_html_viewer_dde(); start_dde($app, $topic, $file); } # XXX change to use IE or NS # XXX Test it... # Return a Win32::OLE object. With the XXX sub show_browser_file { require Win32::OLE ; my $file = shift; if (!defined $browser_ole_obj) { $browser_ole_obj = Win32::OLE->new('InternetExplorer.Application'); } if (defined $file && defined $browser_ole_obj) { $browser_ole_obj->Navigate($file); } } =head2 start_mail_composer($mailaddr) Start a mail composer with $mailaddr as the recipient. =cut sub start_mail_composer { my $mailaddr = shift; if ($mailaddr !~ /^mailto:/) { $mailaddr = "mailto:$mailaddr"; } my $mailto_cmd = get_mail_composer(); start_cmd($mailto_cmd, $mailaddr); } sub get_html_viewer { my $class = get_class_by_ext(".htm") || "htmlfile"; get_reg_cmd($class); } sub get_ps_viewer { my $class = get_class_by_ext(".ps") || "psfile"; get_reg_cmd($class); } sub get_ps_print { my $class = get_class_by_ext(".ps") || "psfile"; get_reg_cmd($class, "print"); } sub get_txt_print { my $class = get_class_by_ext(".txt") || "txtfile"; get_reg_cmd($class, "print"); } sub get_mail_composer { my $cmd = get_reg_cmd("mailto"); if ($cmd) { return $cmd; } else { eval <<'EOF'; use Win32::Registry; my($key_ref, $key_ref2); my $root = "SOFTWARE\\Clients\\Mail"; return unless $main::HKEY_LOCAL_MACHINE->Open($root, $key_ref); my $key_ref2 = []; return unless $key_ref->GetKeys($key_ref2); my $clients = [@$key_ref2]; my $hashref; if ($key_ref->GetValues($hashref)) { unshift @$clients, $hashref->{""}[2]; # default mailer } foreach my $client (@$clients) { if ($main::HKEY_LOCAL_MACHINE->Open("$root\\$client\\Protocols\\mailto\\shell\\open\\command", $key_ref)) { my $hashref; if ($key_ref->GetValues($hashref)) { $cmd = $hashref->{""}[2]; last; } } } EOF warn $@ if $@; return $cmd if defined $cmd; die "Can't send mail"; } } sub get_html_viewer_dde { eval q{ use Win32::Registry; my($app_ref, $topic_ref); return unless $main::HKEY_CLASSES_ROOT->Open('htmlfile\shell\open\ddeexec\Application', $app_ref); return unless $main::HKEY_CLASSES_ROOT->Open('htmlfile\shell\open\ddeexec\Topic', $topic_ref); my($app_hashref, $topic_hashref); return unless $app_ref->GetValues($app_hashref); return unless $topic_ref->GetValues($topic_hashref); ($app_hashref->{""}[2], $topic_hashref->{""}[2]); }; } =head2 start_cmd($cmd, @args...) Start an external program named $cmd. $cmd should be the full path to the executable. @args are passed to the program. The program is spawned, that is, executed in the background. =cut # XXX I got a report where for a print command the shell command # was as follows: # %SystemRoot%\system32\Notepad.exe/p %1 # Note that there is no space between exe and /p! # The user was unable to print, but the editor popped up. # Was it only a typo by the user when reporting, or # is it really this problem? sub start_cmd { my($fullcmd, @args) = @_; my($appname, $base, $cmdline); eval q{ use File::Basename; use Text::ParseWords; # ENV var substitution: my $env = normalize_env(); $fullcmd =~ s/%([^%]+)%/$env->{uc($1)}/g; my(@words) = parse_line('\s+', 1, $fullcmd); $appname = shift @words; $appname =~ s/\"//g; my $argstr = join(" ", @words); $base = basename($appname); $base =~ s/\"//g; $cmdline = $base; my %arg_used; $argstr =~ s/(%(\d))/ $arg_used{$2-1}=1; defined($args[$2-1]) ? $args[$2-1] : "" /eg; $cmdline .= " $argstr"; for my $i (0 .. $#args) { if (!$arg_used{$i}) { $cmdline .= " $args[$i]"; } } if ($DEBUG) { warn "start_cmd: " . $cmdline . "\n(full path: $appname)\n"; } }; warn $@ if $@; my $r; eval q{ use Win32::Process; my $proc; $r = Win32::Process::Create($proc, $appname, $cmdline, 0, NORMAL_PRIORITY_CLASS, "."); }; if ($@) { # try Win32::Spawn (built-in) my $pid; $r = Win32::Spawn($appname, $cmdline, $pid); } $r; } =head2 normalize_env Return a hash reference with all environment variable names changed to uppercase. =cut sub normalize_env { my %env; while(my($k,$v) = each %ENV) { $env{uc($k)} = $v; } \%env; } =head2 start_dde($app, $topic, $arg) Start a program via DDE. (What is $app and $topic?) =cut sub start_dde { my($app, $topic, $arg) = @_; my $r; eval q{ use Win32::DDE::Client; # XXX $app="Netscape";# geht nur mit Netscape und nicht mit "Netscape 4.0" - warum? my $dde = new Win32::DDE::Client($app, $topic); warn "DDE Client with $app and $topic: $dde\n" if $DEBUG; if ($dde->Error) { warn "Unable to initiate DDE connection: " . $dde->Error . "\n"; return; } $r = $dde->Request($arg); $dde->Disconnect; }; $r; } =head1 EXTENSION AND MIME FUNCTIONS =head2 get_reg_cmd($filetype[, $opentype]) Get a command from registry for $filetype. The "open" type is returned, except stated otherwise. =cut sub get_reg_cmd { my($filetype, $opentype) = @_; $opentype = 'open' if !defined $opentype; my $cmd; eval q{ use Win32::Registry; my($reg_key, $key_ref, $hashref); $reg_key = join('\\\\', $filetype, 'shell', $opentype, 'command'); return unless $main::HKEY_CLASSES_ROOT->Open($reg_key, $key_ref); return unless $key_ref->GetValues($hashref); $cmd = $hashref->{""}[2]; }; warn $@ if $@; $cmd; } =head2 get_class_by_ext($ext) Return the class name for the given extension. =cut sub get_class_by_ext { my $ext = shift; my $class; eval q{ use Win32::Registry; my($key_ref, $hashref); return unless $main::HKEY_CLASSES_ROOT->Open($ext, $key_ref); return unless $key_ref->GetValues($hashref); $class = $hashref->{""}[2]; }; warn $@ if $@; $class; } =head2 install_extension(%args) Install a new extension (class) to the registry. The function may take the following key-value parameters: =over 4 =item -extension Required. The extension to be installed. The extension should start with a dot. This can also be an array reference to a number of extensions. =item -name Required. The class name of the new extension. May be something like Excel.Application. =item -icon The (full) path to a default icon file (format should be .ico). =item -open The default open command (used if the file is double-clicked in the explorer). =item -print The default print command. =item -desc An optional description. =item -mime The mime type of the extension (something like text/plain). =back =cut sub install_extension { my(%args) = @_; my $ext = $args{-extension} or die "Missing -extension parameter"; my @ext; push @ext, (ref $ext eq 'ARRAY' ? @$ext : $ext); foreach my $ext (@ext) { if ($ext !~ /^\./) { warn "Extension $ext does not start with dot"; } } my $name = $args{-name} or die "Missing -name parameter"; my $icon = $args{-icon}; my $open = $args{"-open"}; my $print = $args{"-print"}; my $desc = $args{"-desc"}; my $mime = $args{"-mime"}; eval q{ use Win32::Registry; foreach my $ext (@ext) { my $ext_reg; $main::HKEY_CLASSES_ROOT->Create($ext, $ext_reg); $ext_reg->SetValue("", REG_SZ, $name); if (defined $mime) { $ext_reg->SetValueEx("Content Type", 0, REG_SZ, $mime); } } my $name_reg; $main::HKEY_CLASSES_ROOT->Create($name, $name_reg); if (defined $desc) { $name_reg->SetValue("", REG_SZ, $desc); } if (defined $icon) { my $icon_reg; $name_reg->Create("DefaultIcon", $icon_reg); $icon_reg->SetValue("", REG_SZ, $icon); } my $shell_reg; if (defined $open || defined $print) { $name_reg->Create("shell", $shell_reg); } if (defined $open) { my $open_reg; $shell_reg->Create("open", $open_reg); my $command_reg; $open_reg->Create("command", $command_reg); $command_reg->SetValue("", REG_SZ, $open); } if (defined $print) { my $print_reg; $shell_reg->Create("print", $print_reg); my $command_reg; $print_reg->Create("command", $command_reg); $command_reg->SetValue("", REG_SZ, $print); } }; warn $@ if ($@); } =head2 write_uninstall_information(%args) =over 4 =item -appname => $appname The application name. This is required. =item -uninstallstring => $string The command for the uninstall process (???). is required. =item -regowner => $owner Current owner of the registry entry (???). If not specified, the current user is used. =item -version => $version Version number of application. This should consist of a major and minor number. The full application name will be created of C<$appname> and C<$version>. =item -installdate => "YYYY-MM-DD" The date of installation. If not specified, the current date is used. =item -installlocation => $string =item -installsource => $string =item -modifypath => $string =item -publisher => $string =item -urlinfoabout => $string =item -urlupdateinfo => $string =back =cut sub write_uninstall_information { my(%args) = @_; my $appname = delete $args{-appname} || die "-appname missing"; my $uninstallstring = delete $args{-uninstallstring} || die "-uninstallstring missing"; my $regowner = delete $args{-regowner} || get_user_name(); # XXX full name? my $version = delete $args{-version}; my($versionmajor, $versionminor); if (defined $version && $version =~ /^(\d+)\.(\d+)$/) { ($versionmajor, $versionminor) = ($1, $2); } my $appfullname = $appname; if (defined $version) { $appfullname .= " $version"; } my $installdate = delete $args{-installdate} || do { my(@l) = localtime; sprintf "%04d-%02d-%02d", $l[5]+1900, $l[4]+1, $l[3]; }; my $installlocation = delete $args{-installlocation}; my $installsource = delete $args{-installsource}; my $modifypath = delete $args{-modifypath}; my $publisher = delete $args{-publisher}; # XXX maybe s|/|//|g and s|//|///|g (see ext_uninst2.reg example) my $urlinfoabout = delete $args{-urlinfoabout}; my $urlupdateinfo = delete $args{-urlupdateinfo}; if (keys %args) { die "Unknown arguments: " . join(" ", keys %args); } eval <<'EOF'; use Win32::TieRegistry; my $machKey = Win32::TieRegistry->new("LMachine") or die "Can't access HKEY_LOCAL_MACHINE key: $^E"; my $appKey = "Software/Microsoft/Windows/CurrentVersion/Uninstall/" . $appfullname; delete $machKey->{$appKey}; $machKey->{"$appKey/"} = {"/RegOwner" => $regowner, (defined $version ? ("/DisplayVersion" => $version) : ()), "/InstallDate" => $installdate, (defined $installlocation ? ("/InstallLocation" => $installlocation) : ()), (defined $installsource ? ("/InstallSource" => $installsource) : ()), (defined $modifypath ? ("/ModifyPath" => $modifypath) : ()), (defined $publisher ? ("/Publisher" => $publisher) : ()), "/UninstallString" => $uninstallstring, (defined $urlinfoabout ? ("/UrlInfoAbout" => $urlinfoabout) : ()), (defined $urlupdateinfo ? ("/UrlUpdateInfo" => $urlupdateinfo) : ()), (defined $versionmajor ? ("/VersionMajor" => [$versionmajor, "REG_DWORD"]) : ()), (defined $versionminor ? ("/VersionMinor" => [$versionminor, "REG_DWORD"]) : ()), "/DisplayName" => $appfullname, }; EOF warn $@ if $@; } =head1 USER FUNCTIONS =head2 get_user_name Get current windows user. =cut sub get_user_name { my(%args) = @_; # first try the domain user if ($args{-full}) { my $server = get_domain_server(); if (defined $server) { my $userinfo = {}; Win32API::Net::UserGetInfo($server, Win32::LoginName(), 2, $userinfo); if ($userinfo) { return $userinfo->{fullName}; } } } _get_api_function("GetUserName"); if (!$API_FUNC{GetUserName}) { return Win32::LoginName(); } my $max = 256; my $maxb = pack("L", $max); my $login = "\0"x$max; my $b = $API_FUNC{GetUserName}->Call($login, $maxb); if ($b) { substr($login, 0, unpack("L", $maxb)-1); } else { undef; } } =head2 is_administrator Guess if current user has admin rights. =cut sub is_administrator { my $user_name = get_user_name(); if (defined $user_name) { return $user_name =~/^(administrator|admin)$/i ? 1 : 0; } undef; } =head2 get_user_folder($foldertype, $public) Get the folder path for the current user, or, if $public is set to a true value, for the whole system. If $foldertype is not given, the "Personal" subfolder is returned. =cut sub get_user_folder { my($foldertype, $public) = @_; $foldertype = 'Personal' if !defined $foldertype; if ($public) { my $common_folders = { map { $_ => 1 } (qw/AppData Desktop Programs Startup/, 'Start Menu') }; if (exists $common_folders->{$foldertype}) { $foldertype = "Common $foldertype"; } } my $folder; eval q{ use Win32::Registry; my $top_hkey = ($public ? $main::HKEY_LOCAL_MACHINE : $main::HKEY_CURRENT_USER); my($reg_key, $key_ref, $hashref); $reg_key = join('\\\\', qw(SOFTWARE Microsoft Windows CurrentVersion Explorer), 'Shell Folders'); return unless $top_hkey->Open($reg_key, $key_ref); return unless $key_ref->GetValues($hashref); $folder = $hashref->{$foldertype}[2]; }; warn $@ if $@; $folder; } =head2 get_program_folder Get the folder path for the program files (usually C:\Program Files). =cut sub get_program_folder { my $folder; eval q{ use Win32::Registry; my $top_hkey = $main::HKEY_LOCAL_MACHINE; my($reg_key, $key_ref, $hashref); $reg_key = join('\\\\', qw(SOFTWARE Microsoft Windows CurrentVersion)); return unless $top_hkey->Open($reg_key, $key_ref); return unless $key_ref->GetValues($hashref); $folder = $hashref->{"ProgramFilesDir"}[2]; }; warn $@ if $@; $folder; } sub get_special_folder { my($folder_type) = @_; my %sfid = ( ADMINTOOLS => 0x30, # only Windows 2000 ALTSTARTUP => 0x1D, APPDATA => 0x1A, BITBUCKET => 0xA, COMMON_ADMINTOOLS => 0x2F, # only Windows 2000 COMMON_ALTSTARTUP => 0x1D, # only Windows 2000/NT COMMON_APPDATA => 0x23, # only Windows 2000 COMMON_DESKTOPDIRECTORY => 0x19, # only Windows 2000/NT COMMON_DOCUMENTS => 0x2E, # only Windows 2000/NT COMMON_FAVORITES => 0x1F, # only Windows 2000/NT COMMON_PROGRAMS => 0x17, # only Windows 2000/NT COMMON_STARTMENU => 0x16, # only Windows 2000/NT COMMON_STARTUP => 0x18, # only Windows 2000/NT COMMON_TEMPLATES => 0x2D, # only Windows 2000/NT CONTROLS => 0x3, COOKIES => 0x21, DESKTOP => 0x0, DESKTOPDIRECTORY => 0x10, DRIVES => 0x11, FAVORITES => 0x6, FONTS => 0x14, HISTORY => 0x22, INTERNET => 0x1, INTERNET_CACHE => 0x20, LOCAL_APPDATA => 0x1C, # only with MSIE 5.0 MYPICTURES => 0x27, # only with MSIE 5.0 NETHOOD => 0x13, NETWORK => 0x12, PERSONAL => 0x5, PRINTERS => 0x4, PRINTHOOD => 0x1B, PROFILE => 0x28, # only with MSIE 5.0 PROGRAM_FILES => 0x26, # only with MSIE 5.0 PROGRAM_FILES_COMMON => 0x2B, # only Windows 2000/NT PROGRAM_FILES_COMMONX86 => 0x2C, # only Windows 2000 PROGRAM_FILESX86 => 0x2A, # only Windows 2000 PROGRAMS => 0x2, RECENT => 0x8, SENDTO => 0x9, STARTMENU => 0xB, STARTUP => 0x7, SYSTEM => 0x25, # only with MSIE 5.0 SYSTEMX86 => 0x29, # only Windows 2000 TEMPLATES => 0x15, WINDOWS => 0x24, # only with MSIE 5.0 ); my $CSLID = $sfid{$folder_type}; if (!defined $CSLID) { die "Folder type must be one of " . join(", ", keys %sfid) . "\n"; } my $ret; eval q{ my $SHGetSpecialFolderLocation = _get_api_function("SHGetSpecialFolderLocation") || die "Can't get API function for SHGetSpecialFolderLocation"; my $SHGetPathFromIDList = _get_api_function("SHGetPathFromIDList") || die "Can't get API function for SHGetPathFromIDList"; my $IDL = pack("VC", 0,0); my $lResult = $SHGetSpecialFolderLocation->Call(100, $CSLID, $IDL); if ($lResult == 0) { my $sPath = " "x512; my $cb; (undef, $cb) = unpack("VC", $IDL); my $cb_p = pack("C", $cb); $SHGetPathFromIDList->Call($cb_p, $sPath); $sPath =~ s/\0.*//; $ret = $sPath; } else { die "Error in SHGetSpecialFolderLocation"; } }; warn $@ if $@; $ret; } =head2 get_home_dir() Get home directory (from domain server) or from some environment variables. =cut sub get_home_dir { my(%args) = @_; # first try the domain user my $server = get_domain_server(); if (defined $server) { my($userinfo) = {}; Win32API::Net::UserGetInfo($server, Win32::LoginName(), 2, $userinfo); if ($userinfo) { return $userinfo->{homeDir}; } } if (exists $ENV{HOMEDRIVE} && exists $ENV{HOMEPATH}) { return "$ENV{HOMEDRIVE}$ENV{HOMEPATH}"; } $ENV{USERPROFILE} || $ENV{HOMESHARE} || $ENV{HOME}; } =head1 WWW AND NET FUNCTIONS =head2 lwp_auto_proxy($lwp_user_agent) Set the proxy for a LWP::UserAgent object (similar to the unix-centric env_proxy method). Uses the Internet Explorer proxy setting. =cut sub lwp_auto_proxy { my $lwp_user_agent = shift; my $proxy_server; my $proxy_enable = 0; my $proxy_override; eval q{ use Win32::Registry; my($reg_key, $key_ref, $hashref); $reg_key = join('\\\\', qw/Software Microsoft Windows CurrentVersion/, 'Internet Settings'); if ($main::HKEY_CURRENT_USER->Open($reg_key, $key_ref) && $key_ref->GetValues($hashref)) { $proxy_enable = $hashref->{"ProxyEnable"}[2]; $proxy_server = $hashref->{"ProxyServer"}[2]; $proxy_override = $hashref->{"ProxyOverride"}[2]; } }; warn "Proxy settings from registry: enable=$proxy_enable server=$proxy_server override=$proxy_override\n" if $DEBUG; if ($proxy_enable) { # It seems that the following formats are possible: # [http://]proxy[:port] # Fix this format to the one LWP uses... # multiple Proxies are separated by ";" foreach my $single_proxy (split /;/, $proxy_server) { my $proxy_for; if ($single_proxy =~ /^(ftp|http|https)=(.*)/) { $proxy_for = [$1]; $single_proxy = $2; } else { $proxy_for = ['http', 'ftp']; } if ($single_proxy !~ m|^.*://|) { $single_proxy = "http://$single_proxy/"; } warn "Using <$single_proxy> as LWP proxy server setting for @$proxy_for\n" if $DEBUG; $lwp_user_agent->proxy($proxy_for, $single_proxy); } if (defined $proxy_override && $proxy_override eq '') { # XXX There is no way to say that hosts without domain portion # should be no_proxied... So this is a poor excuse... $lwp_user_agent->no_proxy("127.0.0.1", "localhost"); } } } sub get_domain_server { if (eval { require Win32API::Net; 1 }) { my($x1, $server, $x2, $userinfo); Win32API::Net::GetDCName($x1, $x2, $server); return $server; } undef; } =head1 MAIL FUNCTIONS =head2 send_mail(%args) Send an email through MAPI or other means. Some of the following arguments are recognized: =over 4 =item -sender Required. The sender who is sending the mail. =item -passwd The MAPI password (?) =item -recipient The recipient of the mail. =item -subject The subject of the message. =item -body The body text of the message. =back This is from Win32 FAQ. Not tested, because MAPI is not installed on my system. =cut sub send_mail { my(%args) = @_; send_mapi_mail(%args); } sub send_mapi_mail { my(%args) = @_; # Sender's Name and Password # my $sender = $args{-sender} or die "Sender is missing"; my $passwd = $args{-password}; # Create a new MAPI Session # require Win32::OLE; my $session; foreach my $mapiclass ("MAPI.Session", #"MSMAPI.MAPISession" ) { $session = Win32::OLE->new($mapiclass); last if ($session); } if (!$session) { die "Could not create a new MAPI Session: " . Win32::OLE->LastError(); } # Attempt to log on. # my $err = $session->Logon($sender, $passwd); if ($err) { die "Logon failed: $!, " . Win32::OLE->LastError(); } # Add a new message to the Outbox. # my $msg = $session->Outbox->Messages->Add(); # Add the recipient. # my $rcpt = $msg->Recipients->Add(); $rcpt->{Name} = $args{-recipient} or die "Recipient is missing"; $rcpt->Resolve(); # Create a subject and a body. # $msg->{Subject} = $args{-subject} or die "Empty Message"; $msg->{Text} = $args{-body}; # Send the message and log off. # $msg->Update(); $msg->Send(0, 0, 0); $session->Logoff(); 1; } =head1 EXPLORER FUNCTIONS =head2 create_shortcut(%args) Create a shortcut (a desktop link). The following arguments are recognized: =over 4 =item -path Path to program (required). =item -args Additional arguments for the program. =item -icon Path to the .ico icon file. =item -name Title of the program (required). =item -file Specify where to save the .lnk file. If -file is not given, the file will be stored on the current user desktop. The filename will consist of the -name parameter and the .lnk extension. =item -desc Description for the file. =item -wd Working directory of this file. =item -public If true, create a shortcut visible for all users. =item -autostart Create shortlink in Autostart folder. =back =cut sub create_shortcut { my(%args) = @_; my $path = delete $args{-path} || die "Missing -path parameter"; my $args = delete $args{-args}; my $icon = delete $args{-icon}; my $name = delete $args{-name} || die "Missing -name parameter"; my $file = delete $args{-file}; my $desc = delete $args{-desc}; my $wd = delete $args{-wd}; my $public = delete $args{-public} || 0; my $autostart = delete $args{-autostart} || 0; eval q{ use Win32::Shortcut; if (!defined $file) { my $dir; $dir = get_user_folder(($autostart ? "Startup" : "Desktop"), $public); if (!defined $dir) { die "Can't get Desktop or Startup directory"; } $file = join('\\\\', $dir, "$name.lnk"); } my $scut = new Win32::Shortcut; $scut->{Path} = $path; $scut->{Arguments} = $args if defined $args; $scut->{IconLocation} = $icon if defined $icon; $scut->{Description} = $desc if defined $desc; $scut->{WorkingDirectory} = $wd if defined $wd; foreach my $key (keys %args) { $scut->{$key} = $args{$key}; } $scut->{File} = $file; die "Can't save $file" if !$scut->Save; }; warn $@ if ($@); } =head2 create_internet_shortcut(%args) Create an internet shortcut. The following arguments are recognized: =over 4 =item -url URL for the shortcur (required). =item -icon Path to the .ico icon file. =item -name Title of the program (required). Specify where to save the .lnk file. If -file is not given, the file will be stored on the current user desktop. The filename will consist of the -url parameter and the .lnk extension. =item -desc Description for the file (not used yet). =back =cut sub create_internet_shortcut { my(%args) = @_; my $url = delete $args{-url} || die "Missing -url parameter"; my $icon = delete $args{-icon}; my $name = delete $args{-name} || die "Missing -name parameter"; my $file = delete $args{-file}; my $desc = delete $args{-desc}; my $public = delete $args{-public} || 0; eval q{ if (!defined $file) { my $desktop = get_user_folder("Desktop", $public); if (!defined $desktop) { die "Can't get Desktop directory"; } $file = join('\\\\', $desktop, "$name.url"); } my $crlf = "\015\012"; open(URL, ">$file") or die "Can't save $file: $!"; print URL "[InternetShortcut]$crlf"; print URL "URL=$url$crlf"; if (defined $icon) { print URL "IconFile=$icon$crlf"; print URL "IconIndex=0$crlf"; } close URL; }; warn $@ if ($@); } =head2 add_recent_doc($doc) Add the specified document to the list of recent documents. =cut sub add_recent_doc { my $doc = shift; warn "try $doc"; eval q{ my $addtorecentdocs = _get_api_function("SHAddToRecentDocs"); die $@ if !$addtorecentdocs; my $SHARD_PATH = 2; $doc .= "\0"; # XXX notwendig??? $addtorecentdocs->Call($SHARD_PATH, $doc); warn "yeah"; }; warn $@ if $@; } =head2 create_program_group(%args) Create a program group. Following arguments are recognized: =over 4 =item -parent Required. The name of the new program group. =item -files Required. The files to be included into the new program group. The argument may be either a file name or an array with a number of file names. The file names can be either a string or a hash like {-path => 'path', -name => 'name'}. In the latter case, this hash will be used as an argument for create_shortcut. =item -public If true, create a program group in the public section, not in the user section of the start menu. =back =cut sub create_program_group { my(%args) = @_; my $parent = delete $args{-parent} or die "Missing -parent parameter"; my $files = delete $args{-files} or die "Missing -files parameter"; my $public = delete $args{-public} || 0; my @files; push @files, (ref $files eq 'ARRAY' ? @$files : $files); eval q{ use File::Path; use File::Basename; my $progdir = get_user_folder("Programs", $public); die "Can't get user folder." if !$progdir; my $topdir = "$progdir/$parent"; if (!-d $topdir) { mkpath([$topdir], 0, 0755); } foreach my $file (@files) { my %shortcut_args; if (ref $file eq 'HASH') { %shortcut_args = %$file; } else { %shortcut_args = (-path => $file, -name => basename($file), ); } if (exists $shortcut_args{-url}) { $shortcut_args{-file} = "$topdir/$shortcut_args{-name}.url"; create_internet_shortcut(%shortcut_args); } else { $shortcut_args{-file} = "$topdir/$shortcut_args{-name}.lnk"; create_shortcut(%shortcut_args); } } }; warn $@ if $@; } =head1 FILE SYSTEM FUNCTIONS =head2 get_cdrom_drives Return a list of CDROM drives on the system. =cut sub XXX_get_cdrom_drives { my @drives; eval q{ my $DRIVE_CDROM = 5; my $MAX_DOS_DRIVES = 26; my $getlogicaldrives = _get_api_function("GetLogicalDrives"); my $getdrivetype = _get_api_function("GetDriveType"); die $@ if !$getlogicaldrives || !$getdrivetype; my $drives = $getlogicaldrives->Call(); my @drive_bits = split(//, unpack("b*", pack("L", $drives))); # XXX V statt L? for my $i (0 .. $MAX_DOS_DRIVES-1) { if ($drive_bits[$i]) { my $drive_name = chr($i + ord('A')) . ":"; my $drive_type = $getdrivetype->Call($drive_name); push @drives, $drive_name if ($drive_type == $DRIVE_CDROM); } } }; warn $@ if $@; @drives; } sub get_cdrom_drives { get_drives('cdrom'); } =head2 get_drives([$drive_filter]) Return a list of drives on the system. The optional parameter C<$drive_filter> should be a comma-separated string with the possible values C, C (for fixed drives like harddisks), C, C (for net drives), and C (for removable drives like ZIP or floppy disk drives). =cut sub get_drives { my($filter) = @_; my @drives; eval q{ my %drive_filter; if ($filter) { my %drive_def = (cdrom => 5, fixed => 3, ramdisk => 6, remote => 4, removable => 2, ); foreach my $drive (split /,/, $filter) { if ($drive_def{$drive}) { $drive_filter{$drive_def{$drive}}++; } else { die "Unknown drive: $drive, please use any of: " . join(", ", keys %drive_def); } } } my $MAX_DOS_DRIVES = 26; my $getlogicaldrives = _get_api_function("GetLogicalDrives"); my $getdrivetype = _get_api_function("GetDriveType"); die $@ if !$getlogicaldrives || !$getdrivetype; my $drives = $getlogicaldrives->Call(); my @drive_bits = split(//, unpack("b*", pack("L", $drives))); # XXX V statt L? for my $i (0 .. $MAX_DOS_DRIVES-1) { if ($drive_bits[$i]) { my $drive_name = chr($i + ord('A')) . ":"; my $drive_type = $getdrivetype->Call($drive_name); push @drives, $drive_name if !$filter || exists $drive_filter{$drive_type}; } } }; warn $@ if $@; @drives; } =head2 path2unc($path) Expand a normal absolute path to a UNC path. =cut sub path2unc { my $path = shift; if ($path =~ m|^([a-z]):[/\\](.*)|i) { "\\\\" . Win32::NodeName() . "\\" . $1 . "\\" . $2; } else { $path; } } =head1 GUI FUNCTIONS =head2 client_window_region($tk_window) Return maximum region for a window (without borders, title bar, taskbar area). Format is ($x, $y, $width, $height). =cut sub client_window_region { my $top = shift; my $SPI_GETWORKAREA = 48; my $SM_CYCAPTION = 4; #my $SM_CXBORDER = 5; #my $SM_CYBORDER = 6; #my $SM_CXEDGE = 45; #my $SM_CYEDGE = 46; my $SM_CXFRAME = 32; my $SM_CYFRAME = 33; my @extends; _get_api_function("SystemParametersInfo"); _get_api_function("GetSystemMetrics"); if (!$API_FUNC{SystemParametersInfo} || !$API_FUNC{GetSystemMetrics}) { # guess region @extends = (0, 0, $top->screenwidth-24, $top->screenheight-40); } else { my $buf = "\0"x(4*4); # size of RECT structure my $r = $API_FUNC{SystemParametersInfo}->Call($SPI_GETWORKAREA, 0, $buf, 0); # XXX $r überprüfen @extends = unpack("V4", $buf); $extends[2] -= ($extends[0] + $API_FUNC{GetSystemMetrics}->Call($SM_CXFRAME)*2); $extends[3] -= ($extends[1] + $API_FUNC{GetSystemMetrics}->Call($SM_CYFRAME)*2 + $API_FUNC{GetSystemMetrics}->Call($SM_CYCAPTION)); } @extends; } =head2 screen_region($tk_window) Return maximum screen size without taskbar area. =cut sub screen_region { my $top = shift; my $SPI_GETWORKAREA = 48; my @extends; _get_api_function("SystemParametersInfo"); if (!$API_FUNC{SystemParametersInfo}) { # guess region @extends = (0, 0, $top->screenwidth, $top->screenheight-20); } else { my $buf = "\0"x(4*4); # size of RECT structure my $r = $API_FUNC{SystemParametersInfo}->Call($SPI_GETWORKAREA, 0, $buf, 0); # XXX $r überprüfen @extends = unpack("V4", $buf); } @extends; } =head2 maximize($tk_window) Maximize the window. If Win32::API is installed, then the taskbar will not be obscured. =cut sub maximize { my $top = shift; my $showwindow = _get_api_function("ShowWindow"); if (defined $showwindow) { my $SW_SHOWMAXIMIZED = 3; $showwindow->Call(hex($top->frame), $SW_SHOWMAXIMIZED); } else { my @extends = client_window_region($top); $top->geometry("$extends[2]x$extends[3]+$extends[0]+$extends[1]"); } } =head2 get_sys_color($what) Return ($r,$g,$b) values from 0 to 255 for the requested system color. C<$what> is any of: scrollbar, background, activecaption, inactivecaption, menu, window, windowframe, menutext, windowtext, captiontext, activeborder, inactiveborder, appworkspace, highlight, highlighttext, btnface, btnshadow, graytext, btntext, inactivecaptiontext, btnhighlight, 3ddkshadow, 3dlight, infotext, infobk. =cut sub get_sys_color { my $type = shift; my $name2number = {"scrollbar" => 0, "background" => 1, "activecaption" => 2, "inactivecaption" => 3, "menu" => 4, "window" => 5, "windowframe" => 6, "menutext" => 7, "windowtext" => 8, "captiontext" => 9, "activeborder" => 10, "inactiveborder" => 11, "appworkspace" => 12, "highlight" => 13, "highlighttext" => 14, "btnface" => 15, "btnshadow" => 16, "graytext" => 17, "btntext" => 18, "inactivecaptiontext" => 19, "btnhighlight" => 20, "3ddkshadow" => 21, "3dlight" => 22, "infotext" => 23, "infobk" => 24, }; my $number = $name2number->{$type}; return unless defined $number; _get_api_function("GetSysColor"); return unless $API_FUNC{GetSysColor}; my $i = $API_FUNC{GetSysColor}->Call($number); my($r,$g,$b); $b = $i >> 16; $g = ($i >> 8) & 0xff; $r = $i & 0xff; ($r, $g, $b); } =head2 close_dosbox() Closes the dosbox. Handy if starting a GUI program with the standard perl.exe and not wperl.exe. =cut sub close_dosbox { require Win32::Console; Win32::Console::Free(); } =head2 disable_dosbox_close_button() As the function name says :-). Derived from a posting from Jack D. NOT YET TESTED! =cut sub disable_dosbox_close_button { require Win32::GUI; use constant MF_BYCOMMAND => 0; # Get DOS window handle.. my $hWnd = Win32::GUI::GetPerlWindow(); die "Can't get perl window" if !$hWnd; # Get System menu associated with IE Window handle.. my $hMenu = $API_FUNC{GetSystemMenu}->Call($hWnd, 0); if ($hMenu) { # Obtain the number of items in the menu my $menuItemCount = $API_FUNC{GetMenuItemCount}->Call($hMenu); # Remove the Close menu item from the menu # The close item has an ID of 61536. Menu is zero-based so last # menu item is actually the Menu Item Count - 1. for (my $i = $menuItemCount-1; $i >= 0; $i--) { my $ID = $API_FUNC{GetMenuItemID}->Call($hMenu, $i); if ($ID == 61536) { $API_FUNC{RemoveMenu}->Call($hMenu, $ID, MF_BYCOMMAND); warn "Removing $ID ",Win32::FormatMessage(Win32::GetLastError());#XXX remove debug comment } } # Force a redraw of the menu. # This will refresh the titlebar and disable the 'X' button $API_FUNC{DrawMenuBar}->Call($hWnd); } } =head2 keep_on_top($tk_window [, $flag]) Keep the window C<$tk_window> on top. If C is not available, attributes(-topmost) is used if Tk 804.027 is available, otherwise a crude hack with a binding is used instead. If the optional variable C<$flag> is false, "keep on top" is disabled. =cut # Idea by Jack sub keep_on_top { my $mw = shift; my $flag = 1; if (@_) { $flag = shift; } my $HWND_NOTOPMOST = -2; my $HWND_TOPMOST = -1; my $SWP_NOMOVE = 2; my $SWP_NOSIZE = 1; my $set_window_pos = _get_api_function("SetWindowPos"); if ($set_window_pos) { $mw->update; my $WinID = hex($mw->frame); my $callAPI = sub { my($flag) = @_; my($return) = $set_window_pos->Call($WinID,$flag,0,0,0,0,$SWP_NOSIZE|$SWP_NOMOVE); warn "ERROR in api call" unless $return; }; if ($flag) { $callAPI->($HWND_TOPMOST); } else { $callAPI->($HWND_NOTOPMOST); } } elsif ($Tk::VERSION >= 804.027) { $mw->attributes(-topmost => $flag); # XXX test this! } else { warn "No Win32::API available, visibility binding hack"; my $stay_above_after; $mw->bind("" => sub { if ($stay_above_after) { $stay_above_after->cancel; } $stay_above_after = $mw->after (1000, sub { $mw->raise; #Tk->break; undef $stay_above_after; }); } ); } } =head1 MISC FUNCTIONS =head2 sort_cmp_hack($a,$b) "use locale" does not work on Windows. This is a hack to be used in sort for german umlauts. =cut sub sort_cmp_hack { my($s1, $s2) = @_; sort_cmp_hack_transform($s1) cmp sort_cmp_hack_transform($s2); } sub sort_cmp_hack_transform { my $s = shift; $s =~ tr/äöüß/aous/; $s; } # REPO BEGIN # REPO NAME is_in_path /home/e/eserte/src/repository # REPO MD5 81c0124cc2f424c6acc9713c27b9a484 sub is_in_path { my($prog) = @_; return $prog if (file_name_is_absolute($prog) and -f $prog and -x $prog); require Config; my $sep = $Config::Config{'path_sep'} || ':'; foreach (split(/$sep/o, $ENV{PATH})) { if ($^O eq 'MSWin32') { # maybe use $ENV{PATHEXT} like maybe_command in ExtUtils/MM_Win32.pm? return "$_\\$prog" if (-x "$_\\$prog.bat" || -x "$_\\$prog.com" || -x "$_\\$prog.exe" || -x "$_\\$prog.cmd"); } else { return "$_/$prog" if (-x "$_/$prog" && !-d "$_/$prog"); } } undef; } # REPO END # REPO BEGIN # REPO NAME file_name_is_absolute /home/e/eserte/src/repository # REPO MD5 89d0fdf16d11771f0f6e82c7d0ebf3a8 BEGIN { if (eval { require File::Spec; defined &File::Spec::file_name_is_absolute }) { *file_name_is_absolute = \&File::Spec::file_name_is_absolute; } else { *file_name_is_absolute = sub { my $file = shift; my $r; if ($^O eq 'MSWin32') { $r = ($file =~ m;^([a-z]:(/|\\)|\\\\|//);i); } else { $r = ($file =~ m|^/|); } $r; }; } } # REPO END =head1 SEE ALSO L, L, L, L, L, L, L, L, L. =head1 AUTHOR Slaven Rezic =head1 COPYRIGHT Copyright (c) 1999, 2000, 2001, 2002 Slaven Rezic. All rights reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut 1; __END__ Subject: Re: Best way to locate Windows install dir? I think that in practice env(windir) (note lowercase spelling) is indeed the most portable method. It exists and is well-known since Win 3.x. You could also code your own small extension to call the GetWindowsDirectory() API. ---------------------------------------------------------------- Aus perldoc Win32::Clipboard Zitat: bitmap (CF_DIB) The clipboard contains an image, either a bitmap or a picture copied in the clipboard from a graphic application. The data you get is a binary buffer ready to be written to a bitmap (BMP format) file. Example: $image = Win32::Clipboard::GetBitmap(); open BITMAP, ">some.bmp"; binmode BITMAP; print BITMAP $image; close BITMAP; Schreib es in eine temporäre Datei und ruf dann sowas wie system "mspaint -p some.bmp" auf. Das Kommando druckt bei mir (unter WinXPpro) die Datei direkt auf den Standarddrucker. http://board.perl-community.de/cgi-bin/ikonboard/ikonboard.cgi?act=ST;f=12;st=0;t=108;#idx3 HKEY_CURRENT_USER\Control Panel\International\sLanguage DEU=Deutschland ENG=Grossbritanien (vielleicht fuer Msg.pm verwenden)