package Protocol::DBus::Client; use strict; use warnings; =encoding utf-8 =head1 NAME Protocol::DBus::Client =head1 SYNOPSIS my $dbus = Protocol::DBus::Client::system(); $dbus->initialize(); =head1 DESCRIPTION This is the end class for use in DBus client applications. It subclasses L<Protocol::DBus::Peer>. B<NOTE:> This module will automatically send a “Hello†message after authentication completes. That message’s response will be processed automatically. Because this is part of the protocol’s handshake logic rather than something useful for callers, it is abstracted away from the caller. It is neither necessary nor productive for callers to send a “Hello†message. =cut use parent 'Protocol::DBus::Peer'; use Protocol::DBus::Authn; use Protocol::DBus::Connect; use Protocol::DBus::Path; =head1 STATIC FUNCTIONS =head2 system() Creates an instance of this class that includes a connection to the system’s message bus. This does not do authentication; you’ll need to do that via the class’s methods. =cut sub system { my @addrs = Protocol::DBus::Path::system_message_bus(); return _create_local(@addrs); } =head2 login_session() Like C<system()> but for the login session’s message bus. =cut sub login_session { my @addrs = Protocol::DBus::Path::login_session_message_bus(); if (!@addrs) { die "Failed to identify login system message bus!"; } return _create_local(@addrs); } sub _create_local { my ($addr) = @_; my ($socket, $bin_addr) = Protocol::DBus::Connect::create_socket($addr); return __PACKAGE__->new( socket => $socket, address => $bin_addr, human_address => $addr->to_string(), authn_mechanism => 'EXTERNAL', ); } #---------------------------------------------------------------------- =head1 METHODS =head2 $done_yn = I<OBJ>->initialize() This returns truthy once the connection is ready to use and falsy until then. In blocking I/O contexts the call will block. Note that this automatically handles D-Bus’s initial C<Hello> message and its response. Previously this function was called C<do_authn()> and did not wait for the C<Hello> message’s response. The older name is retained as an alias for backward compatibility. =cut sub initialize { my ($self) = @_; if ($self->_connect() && $self->{'_authn'}->go()) { $self->{'_sent_hello'} ||= do { my $connection_name_sr = \do { $self->{'_connection_name'} = undef }; $self->send_call( path => '/org/freedesktop/DBus', interface => 'org.freedesktop.DBus', destination => 'org.freedesktop.DBus', member => 'Hello', )->then( sub { $$connection_name_sr = $_[0]->get_body()->[0]; } ); }; if (!$self->{'_connection_name'}) { GET_MESSAGE: { if (my $msg = $self->SUPER::get_message()) { return 1 if $self->{'_connection_name'}; push @{ $self->{'_pending_received_messages'} }, $msg; redo GET_MESSAGE; } } } } return $self->{'_connection_name'} ? 1 : 0; } sub _connect { my ($self) = @_; local $!; if (!$self->{'_connected'}) { $self->{'_sent_connect'} ||= do { if ( connect $self->{'_socket'}, $self->{'_address'} ) { $self->{'_connected'} = 1; } elsif (!$!{'EINPROGRESS'}) { die "connect($self->{'_human_address'}): $!"; } }; } if (!$self->{'_connected'}) { # This non-blocking connect logic will ordinarily be unneeded # since even in non-blocking mode a UNIX socket connect() doesn’t # normally block. Where such a connect() *will* have to wait is # when the server has no more space for a new connection. my $mask = q<>; vec( $mask, fileno $self->{'_socket'}, 1 ) = 1; my $got = select undef, $mask, undef, 0; if ($got > 0) { my $errno = getsockopt( $self->{'_socket'}, Socket::SOL_SOCKET(), Socket::SO_ERROR() ); if (!defined $errno) { die "getsockopt(SOL_SOCKET, SO_ERROR): $!"; } local $! = unpack 'I', $errno; if (0 + $!) { die "connect($self->{'_human_address'}): $!"; } else { $self->{'_connected'} = 1; } } } return $self->{'_connected'}; } *do_authn = *initialize; #---------------------------------------------------------------------- =head2 $yn = I<OBJ>->init_pending_send() This indicates whether there is data queued up to send for the initialization. Only useful with non-blocking I/O. This function was previously called C<authn_pending_send()>; the former name is retained for backward compatibility. =cut sub init_pending_send { my ($self) = @_; if ($self->{'_connection_name'}) { die "Don’t call this after initialize() is done!"; } return 1 if $self->{'_sent_connect'} && !$self->{'_connected'}; if ($self->{'_sent_hello'}) { return $self->pending_send(); } return $self->{'_authn'}->pending_send(); } *authn_pending_send = \*init_pending_send; #---------------------------------------------------------------------- =head2 $yn = I<OBJ>->supports_unix_fd() Boolean that indicates whether this client supports UNIX FD passing. =cut sub supports_unix_fd { my ($self) = @_; return $self->{'_authn'}->negotiated_unix_fd(); } #---------------------------------------------------------------------- =head2 $msg = I<OBJ>->get_message() Same as in the base class, but for clients the initial “Hello†message and its response are abstracted =cut sub get_message { my ($self) = @_; die "initialize() is not finished!" if !$self->{'_connection_name'}; if ($self->{'_pending_received_messages'} && @{ $self->{'_pending_received_messages'} }) { return shift @{ $self->{'_pending_received_messages'} }; } no warnings 'redefine'; *get_message = Protocol::DBus::Peer->can('get_message'); return $_[0]->get_message(); } =head2 $name = I<OBJ>->get_unique_bus_name() Returns the connection’s unique bus name. C<get_connection_name()> is a historical alias for this method. =cut sub get_unique_bus_name { return $_[0]->{'_connection_name'} || die 'No connection name known yet!'; } BEGIN { *get_connection_name = *get_unique_bus_name; } # undocumented for now sub new { my ($class, %opts) = @_; my $authn = Protocol::DBus::Authn->new( socket => $opts{'socket'}, mechanism => $opts{'authn_mechanism'}, ); my $self = $class->SUPER::new( $opts{'socket'} ); $self->{'_authn'} = $authn; if (my $address = $opts{'address'}) { $self->{'_address'} = $address; $self->{'_human_address'} = $opts{'human_address'}; } else { $self->{'_connected'} = 1; } return $self; } #sub DESTROY { # print "DESTROYED: [$_[0]]\n"; #} 1;