Don't create bus object until constructor is called.
[rox-lib/lack.git] / python / rox / settings.py
blob12738797d7349f35f275368c540b7427ddae2bcb
1 """ROX-Session settings with D-Bus and optional Gnome (gconf) setting
3 Setting and Settings are derived from ROX-Lib's Option and OptionGroup
4 respectively. A Setting sends a dbus message to ROX-Session when changed.
6 Use get_xsettings to get the dbus interface, then create a Settings object
7 with it to pass to each Setting.
8 """
9 import os
10 import rox
11 from rox.options import OptionGroup, Option
12 from rox import OptionsBox
13 import gobject
15 gconf = None
17 _warned_import = False
18 _warned_connect = False
19 _warned_norox = False
21 def get_xsettings():
22 """Returns ROX-Session's Settings dbus interface.
24 Called automatically if and when necessary
25 """
26 global _warned_import
27 global _warned_connect
28 global _warned_norox
29 try:
30 import dbus
31 except ImportError:
32 if not _warned_import:
33 rox.alert("Failed to import dbus module. You probably need "
34 "to install a package with a name like 'python2.3-dbus'"
35 "or 'python2.4-dbus'.\n"
36 "D-BUS can also be downloaded from http://freedesktop.org.")
37 _warned_import = True
38 return None
40 try:
41 if (hasattr(dbus, 'SessionBus')):
42 bus = dbus.SessionBus()
43 else:
44 bus = dbus.Bus(dbus.Bus.TYPE_SESSION)
45 except:
46 if not _warned_connect:
47 rox.alert('Failed to connect to D-BUS session bus. This probably '
48 "means that you're running an old version of ROX-Session "
49 '(or not using ROX-Session at all). Settings cannot be set '
50 "using this program until a newer version of ROX-Session is "
51 "running.")
52 _warned_connect = True
53 return None
55 try:
56 if hasattr(bus, 'get_service'):
57 rox_session = bus.get_service('net.sf.rox.Session')
58 rox_settings = rox_session.get_object('/Settings',
59 'net.sf.rox.Session.Settings')
60 else:
61 rox_settings = dbus.Interface(bus.get_object('net.sf.rox.Session',
62 '/Settings'),
63 'net.sf.rox.Session.Settings')
64 except:
65 if not _warned_norox:
66 rox.alert("ROX-Session doesn't appear to be running (or "
67 "you are running an old version). Changing many of "
68 "these settings will have no effect.")
69 _warned_norox = True
70 return None
72 return rox_settings
74 def get_gconf():
75 """Get GConf connection.
77 Some of the options have corresponding gconf entries; this gets
78 the gconf client connection. It will be called automatically if
79 and when necessary.
80 """
81 global gconf
82 try:
83 import gconf
84 client = gconf.client_get_default ()
85 client.add_dir ("/desktop/gnome/interface",
86 gconf.CLIENT_PRELOAD_NONE)
87 except:
88 client = None
89 return client
91 class Settings(OptionGroup):
92 """A group of options associated with the dbus interface. """
94 program = os.path.basename(rox.app_dir) # For dialog box title
96 def __init__(self, bus = None, client = None):
97 """Constructor
99 bus: ROX-Session's dbus interface. Omit to use default
100 client: gconf client connection. Omit to use default
102 self.options = {}
103 self.callbacks = []
104 self.bus = bus or get_xsettings()
105 self.client = client
107 def notify(self):
108 map(apply, self.callbacks)
109 for option in self:
110 option.has_changed = False
112 def save(self):
113 pass
115 class Setting(Option):
116 def __init__(self, name, default, settings, garbage = False,
117 gconf_key = None):
118 """Constructor
120 name: Option name as sent in dbus message.
121 default: Default value.
122 settings: The group of Settings this one belongs to.
123 garbage: Font and theme changes cause (some versions of?) GTK to
124 update all windows even if they're supposed to have been
125 destroyed. If we've just closed a dialog eg font selection (or
126 menu?), this can cause a crash, so this option forces a garbage
127 collection to make sure there is no stale reference.
128 gconf_key: Optional gconf setting key. If it begins with / it
129 will be treated as the absolute path, otherwise it will
130 have /desktop/gnome/interface/ prepended.
132 self.name = name
133 self.default = default
134 self.settings = settings
135 settings.options[name] = self
136 self.garbage = garbage
137 self.value = None
138 if gconf_key and gconf_key[0] != '/':
139 gconf_key = "/desktop/gnome/interface/" + gconf_key
140 self.gconf_key = gconf_key
141 try:
142 type, value = settings.bus.GetSetting(name)
143 except: #XXX: dbus.DBusException:
144 self._set(default)
145 else:
146 self._set(value, notify = False)
148 def make_gconf_value(self):
149 """Returns value ready to be converted to a GConfValue.
151 Override if necessary. Return a bool, int or string
152 (so the name is slightly misleading).
154 if type(self.default) is str:
155 return str(self.value)
156 else:
157 return self.int_value
159 def pre_notify_hook(self):
160 """Called just before notifying dbus the standard way.
162 Override to perform additional operations and return True
163 if you want to prevent normal notification.
164 Won't be called if there's no bus.
166 return False
168 def post_notify_hook(self):
169 """Called just after notifying dbus the standard way.
171 Override to perform additional operations.
172 Won't be called if there's no bus, but otherwise will be called
173 even if pre_notif_hook() returns True.
175 pass
177 def _set(self, value, notify = True):
178 Option._set(self, value)
179 if not notify: return
181 # This is a separate function because it used to be called via a
182 # GObject idle timeout instead of immediately. But that seems to be
183 # more of a hinrance than a help.
184 def set():
185 if self.garbage:
186 import gc
187 gc.collect()
189 if not self.settings.bus is None:
190 if not self.pre_notify_hook():
191 if type(self.default) is str:
192 self.settings.bus.SetString(self.name, self.value)
193 else:
194 self.settings.bus.SetInt(self.name, self.int_value)
195 self.post_notify_hook()
197 if self.gconf_key:
198 if not self.settings.client:
199 self.settings.client = get_gconf()
200 if self.settings.client:
201 val = self.make_gconf_value()
202 # Unfortunately GConfClient.set can't coerce builtin
203 # types to GConfValues
204 if type(val) is bool:
205 self.settings.client.set_bool(self.gconf_key, val)
206 elif type(val) is int:
207 self.settings.client.set_int(self.gconf_key, val)
208 else:
209 self.settings.client.set_string(self.gconf_key, val)
211 return False
213 if self.garbage:
214 gobject.idle_add(set)
215 else:
216 set()
218 class BoolSetting(Setting):
219 """Bool setting for GConf/D-Bus
221 Option doesn't distinguish between int and bool, but gconf does,
222 so use this for bool options.
224 def __init__(self, name, default, settings, theme, gconf_key = None):
225 Setting.__init__(self, name, default, settings, theme, gconf_key)
226 def make_gconf_value(self):
227 return self.int_value != 0