What: dbus Where: https://chiselapp.com/user/schelte/repository/dbus Description: The DBus project provides a Tcl interface to the dbus message bus system. It allows Tcl programs to send and receive dbus signals, as well as invoke and respond to dbus method calls. Updated: 01/2018 Contact: Schelte Bron
The dbus package is primarily intended for using the dbus interfaces published by other programs. To publish a dbus interface for your own program, use dbif.
Because it may not be immediately obvious how to use the package to communicate with other programs, here's an example based on a real-life question brought up by saedelaere in the Tcl chatroom:
There is a standard for a desktop notifications service, through which applications can generate passive popups (sometimes known as "poptarts") to notify the user in an asynchronous manner of events. This service is available for multiple desktop environments, like GNOME and KDE.
The specification can be found at: http://people.gnome.org/~mccann/docs/notification-spec/notification-spec-latest.html
This specification indicates that the notification service registers under the name "org.freedesktop.Notifications" on the dbus session bus and implements the org.freedesktop.Notifications interface on an object with the path "/org/freedesktop/Notifications". The method to use to popup a notification is called "org.freedesktop.Notifications.Notify". This method takes a whole bunch of arguments, but from the specification it is not completely clear for all arguments what their type should be exactly.
To obtain the required information you can introspect the dbus interface of the application using the following commands:
package require dbus
dbus connect
dbus call -dest org.freedesktop.Notifications /org/freedesktop/Notifications \
org.freedesktop.DBus.Introspectable IntrospectThis returns an XML document that describes the available methods and signals. Look for the Notify method that we are currently interested in:
<method name="Notify">
<annotation value="QVariantMap" name="com.trolltech.QtDBus.QtTypeName.In6"/>
<arg direction="out" type="u"/>
<arg direction="in" type="s" name="app_name"/>
<arg direction="in" type="u" name="replaces_id"/>
<arg direction="in" type="s" name="app_icon"/>
<arg direction="in" type="s" name="summary"/>
<arg direction="in" type="s" name="body"/>
<arg direction="in" type="as" name="actions"/>
<arg direction="in" type="a{sv}" name="hints"/>
<arg direction="in" type="i" name="timeout"/>
</method>The args where direction="in" provide the signature specification we need to pass to the dbus call. Simply concatenate all the type values together.
So now we can call the method as follows:
dbus call -dest org.freedesktop.Notifications -signature susssasa{sv}i \
/org/freedesktop/Notifications org.freedesktop.Notifications Notify \
"My App" 0 "" "Message" "Hello, World!" {} {} -1Not specifically Tcl related, but can be difficult to find: This is how you make the application start automatically when another application calls a method on the dbus name belonging to our application. Create a file with a .service extension under /usr/share/dbus-1/services, with the following contents (assuming we use tk.tcl.wiki.foo as dbus name for the application):
[D-BUS Service] Name=tk.tcl.wiki.foo Exec=/usr/local/bin/foo.tcl
For applications connecting to the system dbus, you would put the .service file under /usr/share/dbus-1/system-services. You may also want to add a User option to indicate under which uid the program should run.
To be allowed to use a symbolic dbus name on the system dbus you also need a file under /etc/dbus-1/system.d that may look something like this:
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<!-- User userid can own this service -->
<policy user="userid">
<allow own="tk.tcl.wiki.foo"/>
</policy>
<policy context="default">
<allow send_interface="tk.tcl.wiki.foo"/>
<allow receive_interface="tk.tcl.wiki.foo"/>
<!-- Allow introspection -->
<allow send_destination="tk.tcl.wiki.foo"
send_interface="org.freedesktop.DBus.Introspectable"/>
</policy>
</busconfig>bll 2017-1-31 Great stuff sbron, thanks. Some more examples:
Get a list of registered names (I use this to get a list of registered media players)
set serial [dbus call session -details \
-handler result \
-dest org.freedesktop.DBus \
/org/freedesktop/DBus \
org.freedesktop.DBus ListNames \
]Set the volume for a media player
set serial [dbus call session -details \
-dest org.mpris.MediaPlayer2.vlc \
-signature ssv \
-handler result \
/org/mpris/MediaPlayer2 \
org.freedesktop.DBus.Properties Set \
org.mpris.MediaPlayer2.Player Volume \
[list d $vol] \
]
sbron 2023-04-12 The dbus package can also be used to have a native file dialog on linux.
ebcfr 2025-08-22
Listen to iio-sensor-proxy and UPower properties
iio-sensor-proxy interface allows to get accelerometer events on laptops running Linux and DBus.
package require dbus
# connect to the system bus
dbus connect system
# instrospect net.hadess.SensorProxy interface
dbus call system -dest net.hadess.SensorProxy \
/net/hadess/SensorProxy \
org.freedesktop.DBus.Introspectable \
IntrospectTo use the interface
# determine if accelermeter is supported (return a boolean value 0/1)
dbus call system -dest net.hadess.SensorProxy \
/net/hadess/SensorProxy \
org.freedesktop.DBus.Properties \
Get "net.hadess.SensorProxy" "HasAccelerometer"
# listen to PropertiesChanged signal
proc handler {desc args} {
puts $desc
puts $args
}
# filter events and register a handler to be called on PropertiesChanged signal
dbus filter system add -interface org.freedesktop.DBus.Properties
dbus listen system /net/hadess/SensorProxy PropertiesChanged handler
# allow iio-sensor-proxy layer to send accelerometer events
dbus call system -dest net.hadess.SensorProxy \
/net/hadess/SensorProxy \
net.hadess.SensorProxy \
ClaimAccelerometer
# if not in a Tk app, wait for events
vwait foreverUnregistering the handler
# disable the sending of events from accelerometer
dbus call system -dest net.hadess.SensorProxy \
/net/hadess/SensorProxy \
net.hadess.SensorProxy \
ReleaseAccelerometer
# unregister the handler
dbus listen system /net/hadess/SensorProxy PropertiesChanged {}Upower allows to get events related to power events (plug/unplug of the main power, laptop lid close) and to access related parameters
# introspect the interface
dbus call system -dest org.freedesktop.UPower \
/org/freedesktop/UPower \
org.freedesktop.DBus.Introspectable \
Introspect
# Enumerates power devices
dbus call system -dest org.freedesktop.UPower \
/org/freedesktop/UPower \
org.freedesktop.UPower \
EnumerateDevicesRegister an event handler for Properties Changed signal
proc handler {desc args} {
puts $desc
puts $args
}
dbus filter system add -interface org.freedesktop.DBus.Properties
dbus listen system /org/freedektop/UPower PropertiesChanged handlerPutting all together (listen signals from both iio-sensor-proxy and UPower)
#! tclsh
package require dbus
set last_orientation normal
proc handler {desc args} {
if {[lindex $args 0] == "net.hadess.SensorProxy"} {
if {[lindex $args {1 0}] == "AccelerometerOrientation"} {
set orientation [lindex $args {1 1}]
if {$orientation != $::last_orientation} {
if {$orientation == "normal"} {
puts "normal"
} elseif {$orientation == "right-up"} {
puts "right-up"
} elseif {$orientation == "left-up"} {
puts "left-up"
} elseif {$orientation == "bottom-up"} {
puts "bottom-up"
}
set ::last_orientation $orientation
}
}
} elseif {[lindex $args 0] == "org.freedesktop.UPower"} {
if {[lindex $args {1 0}] == "LidIsClosed"} {
if {[lindex $args {1 1}] == 1} {
puts "Lid closed"
} else {
puts "Lid opened"
}
} elseif {[lindex $args {1 0}] == "OnBattery"} {
puts "on battery: [lindex $args {1 1}]"
}
}
}
dbus connect system
if {![dbus call system -dest net.hadess.SensorProxy \
/net/hadess/SensorProxy \
org.freedesktop.DBus.Properties\
Get "net.hadess.SensorProxy" "HasAccelerometer"]} {
puts "No accelerometer available"
exit
}
puts "has accelerometer"
# listen to ChangedProperties events
dbus filter system add -interface org.freedesktop.DBus.Properties
dbus listen system /org/freedesktop/UPower PropertiesChanged handler
dbus listen system /net/hadess/SensorProxy PropertiesChanged handler
dbus call system -dest net.hadess.SensorProxy \
/net/hadess/SensorProxy \
net.hadess.SensorProxy \
ClaimAccelerometer
vwait foreverMaking something useful of it : a screen rotator for X11 using accelerometer and laptop lid events via DBus and tk9 systray feature (https://github.com/ebcfr/screenrotator ).
chw 2025-10-10
Bluetooth Serial Port Profile using DBus
No need to use sockets (of the AF_BLUETOOTH kind) in order to implement a stream oriented connection between two endpoints. Due to file descriptor passing, the DBus org.bluez.Profile1 interface let us deal with the underlying Bluetooth sockets as with normal Tcl channels. The following snippet uses the Serial Port Profile (SPP, SP, or RFCOMM) to make both an echo server and client.
# SPP echo server and client using dbus interface.
#
# Command line: <this-script> ?<MAC-address>? ?<BT-adapter>?
#
# If <MAC-address> omitted, become server, else try to connect
# to Serial Port Profile (SPP) on <MAC-address> using dbus.
#
# <BT-adapter> can be specified in cases when the connection
# shall go over another Bluetooth adapter than "hci0".
#
# The participating Bluetooth devices must have been paired and
# authorized in the systems' Bluetooth settings.
#
# Since we are using the system DBus, we need a configuration in
# "/etc/dbus-1/system.d/org.bluez.SerialPort.conf":
#
# <!DOCTYPE busconfig PUBLIC
# "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
# "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
# <busconfig>
# <policy group="bluetooth">
# <allow own="org.bluez.SerialPort"/>
# <allow send_destination="org.bluez.SerialPort"/>
# <allow send_interface="org.bluez.SerialPort"/>
# </policy>
# </busconfig>
#
# It might work partially without this configuration, though.
package require dbus
package require dbif
# UUID for Serial Port Profile (SPP).
set SPP_UUID 00001101-0000-1000-8000-00805f9b34fb
# Get destination MAC address, if any.
set MAC {}
if {[llength $argv] > 0} {
set MAC [apply {{arg} {
set ret {}
if {[scan $arg %x:%x:%x:%x:%x:%x a b c d e f] == 6} {
set a [expr {$a & 0xff}]
set b [expr {$b & 0xff}]
set c [expr {$c & 0xff}]
set d [expr {$d & 0xff}]
set e [expr {$e & 0xff}]
set f [expr {$f & 0xff}]
lappend ret [format %02X:%02X:%02X:%02X:%02X:%02X $a $b $c $d $e $f]
lappend ret [format %02X_%02X_%02X_%02X_%02X_%02X $a $b $c $d $e $f]
}
return $ret
}} [lindex $argv 0]]
}
# Set Bluetooth adapter.
set HCI hci0
if {[llength $argv] > 1} {
set HCI [lindex $argv 1]
}
# Read file handler for SPP (rfcomm) socket providing "echo" semantics.
# Although we deal with an rfcomm socket we get it passed in from dbus
# as a Tcl file channel.
proc echo_service {obj} {
set sock $::SOCK($obj)
set close 0
if {[catch {chan gets $sock line} count]} {
incr close
} elseif {$count < 0} {
if {[chan eof $sock]} {
incr close
} else {
return
}
} elseif {[catch {chan puts $sock $line}]} {
incr close
}
if {$close} {
chan close $sock
unset ::SOCK($obj)
}
}
# Restore blocking mode of standard input.
proc reset_stdin {} {
catch {chan configure stdin -blocking $::STDIN_BLOCKING}
}
# Read lines from standard input and write to SPP (rfcomm) socket.
proc input {obj} {
set sock $::SOCK($obj)
set close 0
if {[catch {chan gets stdin line} count]} {
incr close 2
} elseif {$count < 0} {
if {[chan eof stdin]} {
incr close
} else {
return
}
} elseif {[catch {chan puts $sock $line}]} {
incr close 2
}
if {$close} {
chan close $sock
unset ::SOCK($obj)
incr close -1
reset_stdin
exit $close
}
}
# Read lines from SPP (rfcomm) socket and write to standard output.
proc output {obj} {
set sock $::SOCK($obj)
set close 0
if {[catch {chan gets $sock line} count]} {
puts stderr "Error: $count"
incr close
} elseif {$count < 0} {
if {[chan eof stdin]} {
puts stderr "EOF on socket"
incr close
} else {
return
}
} elseif {[catch {puts $line} err]} {
puts stderr "Error: $err"
incr close
}
if {$close} {
chan close $sock
unset ::SOCK($obj)
reset_stdin
exit 1
}
}
# Give up due to connect timeout.
proc timeout {} {
puts "timeout."
flush stdout
reset_stdin
exit 1
}
# Tclx signal handler for SIGINT.
proc sigint {} {
reset_stdin
exit 1
}
# Callback for ConnectProfile dbus method.
proc on_connect {msg args} {
if {[dict get $msg messagetype] eq "error"} {
puts "failed."
flush stdout
puts stderr "Error: [lindex $args 0]"
reset_stdin
exit 1
}
}
# Array to keep track of SPP (rfcomm) sockets.
array set SOCK {}
# Use system dbus.
set BUS [dbus connect system]
# Provide service "tk.tcl.SerialPort" on system dbus.
set NAME [dbif connect -bus $BUS -yield -replace -noqueue tk.tcl.SerialPort]
if {$NAME eq {}} {
puts stderr "WARNING: 'tk.tcl.SerialPort' not registered in system dbus"
}
# Watch out for new service instance.
dbif listen -bus $BUS -interface [dbus info service] \
[dbus info path] NameLost name {
if {$name eq "tk.tcl.SerialPort"} {
foreach {k v} [array get ::SOCK] {
catch {chan close $v}
}
reset_stdin
exit
}
}
# New SPP connection coming in. Parameters are the dbus object,
# the socket handle (wrapped into a Tcl file channel), and an
# array of additional properties.
dbif method -bus $BUS -interface org.bluez.Profile1 \
/tk/tcl/SerialPort NewConnection {obj:o sock:h props:a{sv}} {} {
set ::SOCK($obj) $sock
chan configure $sock -blocking 0 -buffering line
if {$::MAC ne {}} {
after cancel timeout
puts "success."
puts "Input lines of text; exit program with <Ctrl-D>.\n"
flush stdout
chan event $sock readable [list output $obj]
# Remember blocking mode of standard input.
set ::STDIN_BLOCKING [chan configure stdin -blocking]
catch {
package require Tclx
signal trap SIGINT sigint
}
chan configure stdin -blocking 0 -buffering line
chan event stdin readable [list input $obj]
} else {
chan event $sock readable [list echo_service $obj]
}
}
# Disconnect an SPP connection.
dbif method -bus $BUS -interface org.bluez.Profile1 \
/tk/tcl/SerialPort RequestDisconnect {obj:o} {} {
if {[info exists ::SOCK($obj)]} {
catch {chan close $::SOCK($obj)}
unset ::SOCK($obj)
}
}
# Register SPP and our service in profile manager.
dbus call $BUS -dest org.bluez -signature osa{sv} \
/org/bluez org.bluez.ProfileManager1 RegisterProfile \
/tk/tcl/SerialPort $SPP_UUID \
[dict create AutoConnect [list b 1] \
Name [list s SerialPort] \
Channel [list q 1] \
RequireAuthentication [list b 0] \
RequireAuthorization [list b 0] \
Service [list s $SPP_UUID]]
# Wait a while in case other instance need be torn down.
after 500
# When MAC address specified we need to initiate a connection.
if {$MAC ne {}} {
# Try to connect to remote SPP service.
puts -nonewline "Connecting to [lindex $MAC 0] ... "
flush stdout
if {[catch {
dbus call $BUS -timeout 9000 -dest org.bluez -signature s \
-handler on_connect \
/org/bluez/${HCI}/dev_[lindex $MAC 1] org.bluez.Device1 \
ConnectProfile $SPP_UUID
} err]} {
puts "failed."
flush stdout
puts stderr $err
exit 1
}
# But arrange for extra timeout handling.
after 10000 timeout
}
# Event loop.
vwait forever