312 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			312 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Python
		
	
	
	
| #
 | |
| #
 | |
| #
 | |
| 
 | |
| from __future__ import absolute_import, division, print_function, unicode_literals
 | |
| 
 | |
| from gi.repository import Gtk, GdkPixbuf
 | |
| 
 | |
| from solaar import NAME
 | |
| from . import action as _action, icons as _icons
 | |
| from logitech.unifying_receiver import status as _status
 | |
| 
 | |
| _MENU_ICON_SIZE = Gtk.IconSize.LARGE_TOOLBAR
 | |
| 
 | |
| #
 | |
| #
 | |
| #
 | |
| 
 | |
| def _create_common(icon, menu_activate_callback):
 | |
| 	icon._devices_info = []
 | |
| 
 | |
| 	icon.set_title(NAME)
 | |
| 
 | |
| 	icon._menu_activate_callback = menu_activate_callback
 | |
| 	icon._menu = menu = Gtk.Menu()
 | |
| 
 | |
| 	no_receiver = Gtk.MenuItem.new_with_label('No receiver found')
 | |
| 	no_receiver.set_sensitive(False)
 | |
| 	menu.append(no_receiver)
 | |
| 
 | |
| 	# per-device menu entries will be generated as-needed
 | |
| 	menu.append(Gtk.SeparatorMenuItem.new())
 | |
| 	menu.append(_action.about.create_menu_item())
 | |
| 	menu.append(_action.make('application-exit', 'Quit', Gtk.main_quit).create_menu_item())
 | |
| 	menu.show_all()
 | |
| 
 | |
| 
 | |
| try:
 | |
| 	raise ImportError
 | |
| 	# try:
 | |
| 	# 	from gi.repository import AppIndicator3 as AppIndicator
 | |
| 	# except ImportError:
 | |
| 	# 	from gi.repository import AppIndicator
 | |
| 
 | |
| 	# def create(activate_callback, menu_activate_callback):
 | |
| 	# 	assert activate_callback
 | |
| 	# 	assert menu_activate_callback
 | |
| 
 | |
| 	# 	ind = AppIndicator.Indicator.new('indicator-solaar', _icons.APP_ICON[0], AppIndicator.IndicatorCategory.HARDWARE)
 | |
| 	# 	ind.set_status(AppIndicator.IndicatorStatus.ACTIVE)
 | |
| 
 | |
| 	# 	_create_common(ind, menu_activate_callback)
 | |
| 	# 	ind.set_menu(ind._menu)
 | |
| 
 | |
| 	# 	return ind
 | |
| 
 | |
| 
 | |
| 	# def destroy(ind):
 | |
| 	# 	ind.set_status(AppIndicator.IndicatorStatus.PASSIVE)
 | |
| 	# 	ind.set_menu(None)
 | |
| 
 | |
| 
 | |
| 	# def _update_icon_tooltip(ind, lines_generator):
 | |
| 	# 	pass
 | |
| 
 | |
| 
 | |
| 	# def _update_icon_image(ind, image):
 | |
| 	# 	if isinstance(image, GdkPixbuf.Pixbuf):
 | |
| 	# 		pass
 | |
| 	# 	else:
 | |
| 	# 		ind.set_icon(image)
 | |
| 
 | |
| except ImportError:
 | |
| 
 | |
| 	def create(activate_callback, menu_activate_callback):
 | |
| 		assert activate_callback
 | |
| 		assert menu_activate_callback
 | |
| 
 | |
| 		icon = Gtk.StatusIcon.new_from_icon_name(_icons.APP_ICON[0])
 | |
| 		icon.set_name(NAME)
 | |
| 		icon.set_tooltip_text(NAME)
 | |
| 		icon.connect('activate', activate_callback)
 | |
| 
 | |
| 		_create_common(icon, menu_activate_callback)
 | |
| 		icon.connect('popup_menu',
 | |
| 						lambda icon, button, time, menu:
 | |
| 							icon._menu.popup(None, None, icon.position_menu, icon, button, time),
 | |
| 						icon._menu)
 | |
| 
 | |
| 		return icon
 | |
| 
 | |
| 
 | |
| 	def destroy(icon):
 | |
| 		icon.set_visible(False)
 | |
| 
 | |
| 
 | |
| 	def _update_icon_tooltip(icon, lines_generator):
 | |
| 		tooltip_lines = lines_generator(icon)
 | |
| 		icon.set_tooltip_markup('\n'.join(tooltip_lines).rstrip('\n'))
 | |
| 
 | |
| 
 | |
| 	def _update_icon_image(icon, image):
 | |
| 		if isinstance(image, GdkPixbuf.Pixbuf):
 | |
| 			icon.set_from_pixbuf(image)
 | |
| 		else:
 | |
| 			icon.set_from_icon_name(image)
 | |
| 
 | |
| #
 | |
| #
 | |
| #
 | |
| 
 | |
| def _generate_tooltip_lines(icon):
 | |
| 	yield '<b>%s</b>' % NAME
 | |
| 	yield ''
 | |
| 
 | |
| 	for _, serial, name, status in icon._devices_info:
 | |
| 		if serial is None:  # receiver
 | |
| 			continue
 | |
| 
 | |
| 		yield '<b>%s</b>' % name
 | |
| 
 | |
| 		p = str(status)
 | |
| 		if p:  # does it have any properties to print?
 | |
| 			if status:
 | |
| 				yield '\t%s' % p
 | |
| 			else:
 | |
| 				yield '\t%s <small>(inactive)</small>' % p
 | |
| 		else:
 | |
| 			if status:
 | |
| 				yield '\t<small>no status</small>'
 | |
| 			else:
 | |
| 				yield '\t<small>(inactive)</small>'
 | |
| 		yield ''
 | |
| 
 | |
| 
 | |
| _PIXMAPS = {}
 | |
| def _icon_with_battery(level, active):
 | |
| 	battery_icon = _icons.battery(level)
 | |
| 	name = '%s-%s' % (battery_icon, active)
 | |
| 	if name not in _PIXMAPS:
 | |
| 		mask = _icons.icon_file(_icons.APP_ICON[2], 128)
 | |
| 		if not mask:
 | |
| 			return
 | |
| 		mask = GdkPixbuf.Pixbuf.new_from_file(mask)
 | |
| 		assert mask.get_width() == 128 and mask.get_height() == 128
 | |
| 		mask.saturate_and_pixelate(mask, 0.7, False)
 | |
| 
 | |
| 		battery = _icons.icon_file(battery_icon, 128)
 | |
| 		assert battery
 | |
| 		battery = GdkPixbuf.Pixbuf.new_from_file(battery)
 | |
| 		assert battery.get_width() == 128 and battery.get_height() == 128
 | |
| 		if not active:
 | |
| 			battery.saturate_and_pixelate(battery, 0, True)
 | |
| 
 | |
| 		# TODO can the masking be done at runtime?
 | |
| 		battery.composite(mask, 0, 7, 80, 121, -32, 7, 1, 1, GdkPixbuf.InterpType.NEAREST, 255)
 | |
| 		_PIXMAPS[name] = mask
 | |
| 
 | |
| 	return _PIXMAPS[name]
 | |
| 
 | |
| 
 | |
| def _generate_image(icon):
 | |
| 	if not icon._devices_info:
 | |
| 		return _icons.APP_ICON[-1]
 | |
| 
 | |
| 	battery_status = None
 | |
| 	battery_level = 1000
 | |
| 
 | |
| 	for _, serial, name, status in icon._devices_info:
 | |
| 		if serial is None: # is receiver
 | |
| 			continue
 | |
| 		level = status.get(_status.BATTERY_LEVEL)
 | |
| 		if level is not None and level < battery_level:
 | |
| 			battery_status = status
 | |
| 			battery_level = level
 | |
| 
 | |
| 	if battery_status is None:
 | |
| 		return _icons.APP_ICON[1]
 | |
| 	else:
 | |
| 		pixbuf = _icon_with_battery(battery_level, bool(battery_status))
 | |
| 		if pixbuf:
 | |
| 			return pixbuf
 | |
| 		else:
 | |
| 			return _icons.APP_ICON[1]
 | |
| 
 | |
| #
 | |
| #
 | |
| #
 | |
| 
 | |
| def _add_device(icon, device):
 | |
| 	index = None
 | |
| 	for idx, (rserial, _, _, _) in enumerate(icon._devices_info):
 | |
| 		if rserial == device.receiver.serial:
 | |
| 			index = idx + 1
 | |
| 			break
 | |
| 	assert index is not None
 | |
| 
 | |
| 	device_info = (device.receiver.serial, device.serial, device.name, device.status)
 | |
| 	icon._devices_info.insert(index, device_info)
 | |
| 
 | |
| 	menu_item = Gtk.ImageMenuItem.new_with_label('    ' + device.name)
 | |
| 	icon._menu.insert(menu_item, index)
 | |
| 	menu_item.set_image(Gtk.Image())
 | |
| 	menu_item.show_all()
 | |
| 	menu_item.connect('activate', icon._menu_activate_callback, device.receiver.path, icon)
 | |
| 
 | |
| 	return index
 | |
| 
 | |
| 
 | |
| def _remove_device(icon, index):
 | |
| 	# print ("remove device", index)
 | |
| 	assert index is not None
 | |
| 	del icon._devices_info[index]
 | |
| 	menu_items = icon._menu.get_children()
 | |
| 	icon._menu.remove(menu_items[index])
 | |
| 
 | |
| 
 | |
| def _add_receiver(icon, receiver):
 | |
| 	device_info = (receiver.serial, None, receiver.name, None)
 | |
| 	icon._devices_info.insert(0, device_info)
 | |
| 
 | |
| 	menu_item = Gtk.ImageMenuItem.new_with_label(receiver.name)
 | |
| 	icon._menu.insert(menu_item, 0)
 | |
| 	icon_set = _icons.device_icon_set(receiver.name)
 | |
| 	menu_item.set_image(Gtk.Image().new_from_icon_set(icon_set, _MENU_ICON_SIZE))
 | |
| 	menu_item.show_all()
 | |
| 	menu_item.connect('activate', icon._menu_activate_callback, receiver.path, icon)
 | |
| 
 | |
| 	icon._devices_info.insert(1, ('-', None, None, None))
 | |
| 	separator = Gtk.SeparatorMenuItem.new()
 | |
| 	separator.set_visible(True)
 | |
| 	icon._menu.insert(separator, 1)
 | |
| 
 | |
| 	return 0
 | |
| 
 | |
| 
 | |
| def _remove_receiver(icon, receiver):
 | |
| 	index = 0
 | |
| 	found = False
 | |
| 	while index < len(icon._devices_info):
 | |
| 		rserial, _, _, _ = icon._devices_info[index]
 | |
| 		# print ("remove receiver", index, rserial)
 | |
| 		if rserial == receiver.serial:
 | |
| 			found = True
 | |
| 			_remove_device(icon, index)
 | |
| 		elif found and rserial == '-':
 | |
| 			_remove_device(icon, index)
 | |
| 			break
 | |
| 		else:
 | |
| 			index += 1
 | |
| 
 | |
| 
 | |
| def _update_menu_item(icon, index, device_status):
 | |
| 	menu_items = icon._menu.get_children()
 | |
| 	menu_item = menu_items[index]
 | |
| 
 | |
| 	image = menu_item.get_image()
 | |
| 	battery_level = device_status.get(_status.BATTERY_LEVEL)
 | |
| 	# print ("device_status", dict(device_status), battery_level)
 | |
| 	image.set_from_icon_name(_icons.battery(battery_level), _MENU_ICON_SIZE)
 | |
| 	image.set_sensitive(bool(device_status))
 | |
| 	# menu_item.set_sensitive(bool(device_status))
 | |
| 
 | |
| #
 | |
| #
 | |
| #
 | |
| 
 | |
| def update(icon, device=None):
 | |
| 	# print ("icon update", device, icon._devices_info)
 | |
| 
 | |
| 	if device is not None:
 | |
| 		if device.kind is None:
 | |
| 			# receiver
 | |
| 			receiver = device
 | |
| 			if receiver:
 | |
| 				index = None
 | |
| 				for idx, (rserial, _, _, _) in enumerate(icon._devices_info):
 | |
| 					if rserial == receiver.serial:
 | |
| 						index = idx
 | |
| 						break
 | |
| 
 | |
| 				if index is None:
 | |
| 					_add_receiver(icon, receiver)
 | |
| 			else:
 | |
| 				_remove_receiver(icon, receiver)
 | |
| 
 | |
| 		else:
 | |
| 			# peripheral
 | |
| 			index = None
 | |
| 			for idx, (rserial, serial, name, _) in enumerate(icon._devices_info):
 | |
| 				if rserial == device.receiver.serial and serial == device.serial:
 | |
| 					index = idx
 | |
| 
 | |
| 			if device.status is None:
 | |
| 				# was just unpaired
 | |
| 				assert index is not None
 | |
| 				_remove_device(icon, index)
 | |
| 			else:
 | |
| 				if index is None:
 | |
| 					index = _add_device(icon, device)
 | |
| 				_update_menu_item(icon, index, device.status)
 | |
| 
 | |
| 		menu_items = icon._menu.get_children()
 | |
| 		no_receivers_index = len(icon._devices_info)
 | |
| 		menu_items[no_receivers_index].set_visible(not icon._devices_info)
 | |
| 		menu_items[no_receivers_index + 1].set_visible(not icon._devices_info)
 | |
| 
 | |
| 	_update_icon_tooltip(icon, _generate_tooltip_lines)
 | |
| 	_update_icon_image(icon, _generate_image(icon))
 | |
| 
 | |
| 	# print ("icon updated", device, icon._devices_info)
 |