236 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			236 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Python
		
	
	
	
| #
 | |
| #
 | |
| #
 | |
| 
 | |
| from gi.repository import (Gtk, Gdk)
 | |
| 
 | |
| from logitech.devices import constants as C
 | |
| 
 | |
| 
 | |
| _DEVICE_ICON_SIZE = Gtk.IconSize.DND
 | |
| _STATUS_ICON_SIZE = Gtk.IconSize.DIALOG
 | |
| _PLACEHOLDER = '~'
 | |
| 
 | |
| 
 | |
| def _find_children(container, *child_names):
 | |
| 	def _iterate_children(widget, names, result, count):
 | |
| 		wname = widget.get_name()
 | |
| 		if wname in names:
 | |
| 			index = names.index(wname)
 | |
| 			names[index] = None
 | |
| 			result[index] = widget
 | |
| 			count -= 1
 | |
| 
 | |
| 		if count > 0 and isinstance(widget, Gtk.Container):
 | |
| 			for w in widget:
 | |
| 				count = _iterate_children(w, names, result, count)
 | |
| 				if count == 0:
 | |
| 					break
 | |
| 
 | |
| 		return count
 | |
| 
 | |
| 	names = list(child_names)
 | |
| 	count = len(names)
 | |
| 	result = [None] * count
 | |
| 	_iterate_children(container, names, result, count)
 | |
| 	return result if count > 1 else result[0]
 | |
| 
 | |
| 
 | |
| def _update_receiver_box(box, receiver):
 | |
| 	label, buttons_box = _find_children(box, 'receiver-status', 'receiver-buttons')
 | |
| 	label.set_text(receiver.text or '')
 | |
| 	buttons_box.set_visible(receiver.code >= C.STATUS.CONNECTED)
 | |
| 
 | |
| 
 | |
| def _update_device_box(frame, devstatus):
 | |
| 	if devstatus is None:
 | |
| 		frame.set_visible(False)
 | |
| 		frame.set_name(_PLACEHOLDER)
 | |
| 		return
 | |
| 
 | |
| 	frame.set_visible(True)
 | |
| 	if frame.get_name() != devstatus.name:
 | |
| 		frame.set_name(devstatus.name)
 | |
| 		icon = _find_children(frame, 'device-icon')
 | |
| 		icon.set_from_icon_name(devstatus.name, _DEVICE_ICON_SIZE)
 | |
| 		icon.set_tooltip_text(devstatus.name)
 | |
| 
 | |
| 	expander = _find_children(frame, 'device-expander')
 | |
| 	if devstatus.code < C.STATUS.CONNECTED:
 | |
| 		expander.set_sensitive(False)
 | |
| 		expander.set_expanded(False)
 | |
| 		expander.set_label('<big><b>%s</b></big>\n%s' % (devstatus.name, devstatus.text))
 | |
| 		return
 | |
| 
 | |
| 	expander.set_sensitive(True)
 | |
| 	status_icons = expander.get_child().get_children()
 | |
| 
 | |
| 	texts = []
 | |
| 
 | |
| 	light_icon = status_icons[-2]
 | |
| 	light_level = getattr(devstatus, C.PROPS.LIGHT_LEVEL, None)
 | |
| 	if light_level is None:
 | |
| 		light_icon.set_visible(False)
 | |
| 	else:
 | |
| 		light_icon.set_visible(True)
 | |
| 		icon_name = 'light_%02d' % (20 * ((light_level + 50) // 100))
 | |
| 		light_icon.set_from_icon_name(icon_name, _STATUS_ICON_SIZE)
 | |
| 		tooltip = 'Light: %d lux' % light_level
 | |
| 		light_icon.set_tooltip_text(tooltip)
 | |
| 		texts.append(tooltip)
 | |
| 
 | |
| 	battery_icon = status_icons[-1]
 | |
| 	battery_level = getattr(devstatus, C.PROPS.BATTERY_LEVEL, None)
 | |
| 	if battery_level is None:
 | |
| 		battery_icon.set_sensitive(False)
 | |
| 		battery_icon.set_from_icon_name('battery_unknown', _STATUS_ICON_SIZE)
 | |
| 		battery_icon.set_tooltip_text('Battery: unknown')
 | |
| 	else:
 | |
| 		battery_icon.set_sensitive(True)
 | |
| 		icon_name = 'battery_%02d' % (20 * ((battery_level + 10) // 20))
 | |
| 		battery_icon.set_from_icon_name(icon_name, _STATUS_ICON_SIZE)
 | |
| 		tooltip = 'Battery: %d%%' % battery_level
 | |
| 		battery_icon.set_tooltip_text(tooltip)
 | |
| 		texts.append(tooltip)
 | |
| 
 | |
| 	battery_status = getattr(devstatus, C.PROPS.BATTERY_STATUS, None)
 | |
| 	if battery_status is not None:
 | |
| 		texts.append(battery_status)
 | |
| 		battery_icon.set_tooltip_text(battery_icon.get_tooltip_text() + '\n' + battery_status)
 | |
| 
 | |
| 	if texts:
 | |
| 		expander.set_label('<big><b>%s</b></big>\n%s' % (devstatus.name, ', '.join(texts)))
 | |
| 	else:
 | |
| 		expander.set_label('<big><b>%s</b></big>\n%s' % (devstatus.name, devstatus.text))
 | |
| 
 | |
| 
 | |
| def update(window, receiver, devices, icon_name=None):
 | |
| 	if window and window.get_child():
 | |
| 		if icon_name is not None:
 | |
| 			window.set_icon_name(icon_name)
 | |
| 
 | |
| 		controls = list(window.get_child().get_children())
 | |
| 		_update_receiver_box(controls[0], receiver)
 | |
| 		for index in range(1, len(controls)):
 | |
| 			_update_device_box(controls[index], devices.get(index))
 | |
| 
 | |
| 
 | |
| def _receiver_box(rstatus):
 | |
| 	box = Gtk.HBox(homogeneous=False, spacing=8)
 | |
| 	box.set_border_width(4)
 | |
| 
 | |
| 	icon = Gtk.Image.new_from_icon_name(rstatus.name, _DEVICE_ICON_SIZE)
 | |
| 	icon.set_alignment(0.5, 0)
 | |
| 	icon.set_tooltip_text(rstatus.name)
 | |
| 	box.pack_start(icon, False, False, 0)
 | |
| 
 | |
| 	label = Gtk.Label('Initializing...')
 | |
| 	label.set_alignment(0, 0.5)
 | |
| 	label.set_name('receiver-status')
 | |
| 	box.pack_start(label, True, True, 0)
 | |
| 
 | |
| 	toolbar = Gtk.Toolbar()
 | |
| 	toolbar.set_style(Gtk.ToolbarStyle.ICONS)
 | |
| 	toolbar.set_name('receiver-buttons')
 | |
| 	toolbar.set_show_arrow(False)
 | |
| 	toolbar.set_icon_size(Gtk.IconSize.BUTTON)
 | |
| 	box.pack_end(toolbar, False, False, 0)
 | |
| 
 | |
| 	def _action(button, function, params):
 | |
| 		button.set_sensitive(False)
 | |
| 		function(button, *params)
 | |
| 		button.set_sensitive(True)
 | |
| 
 | |
| 	def _add_button(name, icon, action):
 | |
| 		button = Gtk.ToolButton()
 | |
| 		button.set_icon_name(icon)
 | |
| 		button.set_tooltip_text(name)
 | |
| 		if action:
 | |
| 			function = action[0]
 | |
| 			params = action[1:]
 | |
| 			button.connect('clicked', _action, function, params)
 | |
| 		else:
 | |
| 			button.set_sensitive(False)
 | |
| 		toolbar.insert(button, -1)
 | |
| 
 | |
| 	_add_button('Scan for devices', 'reload', rstatus.refresh)
 | |
| 	_add_button('Pair new device', 'add', rstatus.pair)
 | |
| 
 | |
| 	box.show_all()
 | |
| 	toolbar.set_visible(False)
 | |
| 	return box
 | |
| 
 | |
| 
 | |
| def _device_box():
 | |
| 	box = Gtk.HBox(homogeneous=False, spacing=8)
 | |
| 	box.set_border_width(4)
 | |
| 
 | |
| 	icon = Gtk.Image()
 | |
| 	icon.set_alignment(0.5, 0)
 | |
| 	icon.set_name('device-icon')
 | |
| 	box.pack_start(icon, False, False, 0)
 | |
| 
 | |
| 	expander = Gtk.Expander()
 | |
| 	expander.set_use_markup(True)
 | |
| 	expander.set_spacing(4)
 | |
| 	expander.set_name('device-expander')
 | |
| 	box.pack_start(expander, True, True, 1)
 | |
| 
 | |
| 	ebox = Gtk.HBox(False, 8)
 | |
| 	battery_icon = Gtk.Image.new_from_icon_name('battery_unknown', _STATUS_ICON_SIZE)
 | |
| 	ebox.pack_end(battery_icon, False, True, 0)
 | |
| 	light_icon = Gtk.Image.new_from_icon_name('light_unknown', _STATUS_ICON_SIZE)
 | |
| 	ebox.pack_end(light_icon, False, True, 0)
 | |
| 	expander.add(ebox)
 | |
| 
 | |
| 	frame = Gtk.Frame()
 | |
| 	frame.add(box)
 | |
| 	frame.show_all()
 | |
| 	frame.set_visible(False)
 | |
| 	return frame
 | |
| 
 | |
| 
 | |
| def create(title, rstatus, systray=False):
 | |
| 	window = Gtk.Window()
 | |
| 	window.set_title(title)
 | |
| 	window.set_role('status-window')
 | |
| 
 | |
| 	vbox = Gtk.VBox(homogeneous=False, spacing=4)
 | |
| 	vbox.set_border_width(4)
 | |
| 
 | |
| 	vbox.add(_receiver_box(rstatus))
 | |
| 	for i in range(1, 1 + rstatus.max_devices):
 | |
| 		vbox.add(_device_box())
 | |
| 	vbox.set_visible(True)
 | |
| 	window.add(vbox)
 | |
| 
 | |
| 	geometry = Gdk.Geometry()
 | |
| 	geometry.min_width = 300
 | |
| 	geometry.min_height = 40
 | |
| 	window.set_geometry_hints(vbox, geometry, Gdk.WindowHints.MIN_SIZE)
 | |
| 
 | |
| 	window.set_resizable(False)
 | |
| 	window.set_default_size(geometry.min_width, geometry.min_height)
 | |
| 
 | |
| 	if systray:
 | |
| 		window.set_keep_above(True)
 | |
| 		window.set_deletable(False)
 | |
| 		window.set_decorated(False)
 | |
| 		window.set_position(Gtk.WindowPosition.MOUSE)
 | |
| 		window.set_type_hint(Gdk.WindowTypeHint.MENU)
 | |
| 		window.set_skip_taskbar_hint(True)
 | |
| 		window.set_skip_pager_hint(True)
 | |
| 	else:
 | |
| 		window.set_position(Gtk.WindowPosition.CENTER)
 | |
| 		window.connect('delete-event', Gtk.main_quit)
 | |
| 		window.present()
 | |
| 
 | |
| 	return window
 | |
| 
 | |
| 
 | |
| def toggle(_, window):
 | |
| 	if window.get_visible():
 | |
| 		window.hide()
 | |
| 	else:
 | |
| 		window.present()
 |