459 lines
14 KiB
Python
459 lines
14 KiB
Python
|
#!/usr/bin/env python
|
||
|
|
||
|
import sys
|
||
|
|
||
|
from direct.showbase.ShowBase import ShowBase
|
||
|
from direct.showbase.DirectObject import DirectObject
|
||
|
from panda3d.core import InputDeviceManager, InputDevice
|
||
|
from panda3d.core import VBase4, Vec2
|
||
|
from panda3d.core import TextNode
|
||
|
from direct.gui.DirectGui import (
|
||
|
DGG,
|
||
|
DirectFrame,
|
||
|
DirectButton,
|
||
|
DirectLabel,
|
||
|
DirectScrolledFrame,
|
||
|
DirectSlider,
|
||
|
)
|
||
|
|
||
|
|
||
|
class Main(ShowBase):
|
||
|
def __init__(self):
|
||
|
super().__init__()
|
||
|
base.disableMouse()
|
||
|
self.accept("escape", sys.exit)
|
||
|
self.device_connectivity_monitor = DeviceConnectivityMonitor()
|
||
|
|
||
|
|
||
|
class DeviceConnectivityMonitor(DirectObject):
|
||
|
def __init__(self):
|
||
|
super().__init__()
|
||
|
self.mgr = InputDeviceManager.get_global_ptr()
|
||
|
self.create_device_menu()
|
||
|
|
||
|
self.devices = {}
|
||
|
for device in self.mgr.get_devices():
|
||
|
self.connect_device(device)
|
||
|
|
||
|
self.accept("connect-device", self.connect_device)
|
||
|
self.accept("disconnect-device", self.disconnect_device)
|
||
|
|
||
|
def create_device_menu(self):
|
||
|
self.current_panel = None
|
||
|
self.buttons = {}
|
||
|
self.devices_frame = DirectScrolledFrame(
|
||
|
frameSize=VBase4(
|
||
|
0,
|
||
|
base.a2dLeft*-0.75,
|
||
|
base.a2dBottom - base.a2dTop,
|
||
|
0,
|
||
|
),
|
||
|
frameColor=VBase4(0, 0, 0.25, 1.0),
|
||
|
canvasSize=VBase4(
|
||
|
0,
|
||
|
base.a2dLeft*-0.75,
|
||
|
0,
|
||
|
0,
|
||
|
),
|
||
|
scrollBarWidth=0.08,
|
||
|
manageScrollBars=True,
|
||
|
autoHideScrollBars=True,
|
||
|
pos=(base.a2dLeft, 0, base.a2dTop),
|
||
|
parent=base.aspect2d,
|
||
|
)
|
||
|
|
||
|
self.devices_frame.setCanvasSize()
|
||
|
|
||
|
def create_menu_button(self, device):
|
||
|
button = DirectButton(
|
||
|
command=self.switch_to_panel,
|
||
|
extraArgs=[device],
|
||
|
text=device.name,
|
||
|
text_scale=0.05,
|
||
|
text_align=TextNode.ALeft,
|
||
|
text_fg=VBase4(0.0, 0.0, 0.0, 1.0),
|
||
|
text_pos=Vec2(0.01, base.a2dBottom / 10.0),
|
||
|
relief=1,
|
||
|
pad=Vec2(0.01, 0.01),
|
||
|
frameColor=VBase4(0.8, 0.8, 0.8, 1.0),
|
||
|
frameSize=VBase4(
|
||
|
0.0,
|
||
|
base.a2dLeft*-0.75 - 0.081, # 0.08=Scrollbar, 0.001=inaccuracy
|
||
|
base.a2dBottom / 5.0,
|
||
|
0.0,
|
||
|
),
|
||
|
parent=self.devices_frame.getCanvas(),
|
||
|
)
|
||
|
self.buttons[device] = button
|
||
|
|
||
|
def destroy_menu_button(self, device):
|
||
|
self.buttons[device].detach_node()
|
||
|
del self.buttons[device]
|
||
|
|
||
|
def refresh_device_menu(self):
|
||
|
self.devices_frame['canvasSize'] = VBase4(
|
||
|
0,
|
||
|
base.a2dLeft*-0.75,
|
||
|
base.a2dBottom / 5.0 * len(self.buttons),
|
||
|
0,
|
||
|
)
|
||
|
self.devices_frame.setCanvasSize()
|
||
|
sorted_buttons = sorted(self.buttons.items(), key=lambda i: i[0].name)
|
||
|
for idx, (dev, button) in enumerate(sorted_buttons):
|
||
|
button.set_pos(
|
||
|
0,
|
||
|
0,
|
||
|
(base.a2dBottom / 5.0) * idx,
|
||
|
)
|
||
|
|
||
|
def switch_to_panel(self, device):
|
||
|
if self.current_panel is not None:
|
||
|
self.devices[self.current_panel].hide()
|
||
|
self.current_panel = device
|
||
|
self.devices[self.current_panel].show()
|
||
|
|
||
|
def connect_device(self, device):
|
||
|
if device in self.devices:
|
||
|
return
|
||
|
self.devices[device] = DeviceMonitor(device)
|
||
|
self.switch_to_panel(device)
|
||
|
self.create_menu_button(device)
|
||
|
self.refresh_device_menu()
|
||
|
|
||
|
def disconnect_device(self, device):
|
||
|
self.devices[device].deactivate()
|
||
|
del self.devices[device]
|
||
|
if self.current_panel == device:
|
||
|
self.current_panel = None
|
||
|
if len(self.devices) > 0:
|
||
|
active_device = sorted(
|
||
|
self.devices.keys(),
|
||
|
key=lambda d: d.name,
|
||
|
)[0]
|
||
|
self.switch_to_panel(active_device)
|
||
|
|
||
|
self.destroy_menu_button(device)
|
||
|
self.refresh_device_menu()
|
||
|
|
||
|
|
||
|
class DeviceMonitor(DirectObject):
|
||
|
def __init__(self, device):
|
||
|
super().__init__()
|
||
|
self.device = device
|
||
|
self.create_panel()
|
||
|
self.activate()
|
||
|
self.hide()
|
||
|
|
||
|
def activate(self):
|
||
|
print("Device connected")
|
||
|
print(" Name : {}".format(self.device.name))
|
||
|
print(" Type : {}".format(self.device.device_class.name))
|
||
|
print(" Manufacturer: {}".format(self.device.manufacturer))
|
||
|
print(" ID : {:04x}:{:04x}".format(self.device.vendor_id,
|
||
|
self.device.product_id))
|
||
|
axis_names = [axis.axis.name for axis in self.device.axes]
|
||
|
print(" Axes : {} ({})".format(len(self.device.axes),
|
||
|
', '.join(axis_names)))
|
||
|
button_names = [button.handle.name for button in self.device.buttons]
|
||
|
print(" Buttons : {} ({})".format(len(self.device.buttons),
|
||
|
', '.join(button_names)))
|
||
|
|
||
|
base.attachInputDevice(self.device)
|
||
|
|
||
|
self.task = base.taskMgr.add(
|
||
|
self.update,
|
||
|
"Monitor for {}".format(self.device.name),
|
||
|
sort=10,
|
||
|
)
|
||
|
|
||
|
def deactivate(self):
|
||
|
print("\"{}\" disconnected".format(self.device.name))
|
||
|
base.taskMgr.remove(self.task)
|
||
|
self.panel.detach_node()
|
||
|
|
||
|
def create_panel(self):
|
||
|
panel_width = base.a2dLeft * -0.25 + base.a2dRight
|
||
|
scroll_bar_width = 0.08
|
||
|
# NOTE: -0.001 because thanks to inaccuracy the vertical bar appears...
|
||
|
canvas_width = panel_width - scroll_bar_width - 0.001
|
||
|
canvas_height = base.a2dBottom - base.a2dTop
|
||
|
|
||
|
self.panel = DirectScrolledFrame(
|
||
|
frameSize=VBase4(
|
||
|
0,
|
||
|
panel_width,
|
||
|
canvas_height,
|
||
|
0,
|
||
|
),
|
||
|
frameColor=VBase4(0.8, 0.8, 0.8, 1),
|
||
|
canvasSize=VBase4(
|
||
|
0,
|
||
|
canvas_width,
|
||
|
canvas_height,
|
||
|
0,
|
||
|
),
|
||
|
scrollBarWidth=scroll_bar_width,
|
||
|
manageScrollBars=True,
|
||
|
autoHideScrollBars=True,
|
||
|
pos=(base.a2dLeft * 0.25, 0, base.a2dTop),
|
||
|
parent=base.aspect2d,
|
||
|
)
|
||
|
panel_canvas = self.panel.getCanvas()
|
||
|
offset = -0.0
|
||
|
|
||
|
# Style sheets
|
||
|
|
||
|
half_width_entry = dict(
|
||
|
frameSize=VBase4(
|
||
|
0,
|
||
|
canvas_width / 2,
|
||
|
-0.1,
|
||
|
0,
|
||
|
),
|
||
|
parent=panel_canvas,
|
||
|
frameColor=VBase4(0.8, 0.8, 0.8, 1),
|
||
|
)
|
||
|
left_aligned_small_text = dict(
|
||
|
text_align=TextNode.ALeft,
|
||
|
text_scale=0.05,
|
||
|
text_fg=VBase4(0,0,0,1),
|
||
|
text_pos=(0.05, -0.06),
|
||
|
)
|
||
|
half_width_text_frame = dict(
|
||
|
**half_width_entry,
|
||
|
**left_aligned_small_text,
|
||
|
)
|
||
|
|
||
|
header = dict(
|
||
|
frameSize=VBase4(
|
||
|
0,
|
||
|
canvas_width,
|
||
|
-0.1,
|
||
|
0,
|
||
|
),
|
||
|
parent=panel_canvas,
|
||
|
frameColor=VBase4(0.6, 0.6, 0.6, 1),
|
||
|
text_align=TextNode.ALeft,
|
||
|
text_scale=0.1,
|
||
|
text_fg=VBase4(0,0,0,1),
|
||
|
text_pos=(0.05, -0.075),
|
||
|
)
|
||
|
|
||
|
# Basic device data (name, device class, manufacturer, USB ID)
|
||
|
|
||
|
self.device_header = DirectLabel(
|
||
|
text="Device data",
|
||
|
pos=(0, 0, offset),
|
||
|
**header,
|
||
|
)
|
||
|
offset -= 0.1
|
||
|
|
||
|
def add_data_entry(offset, label, text):
|
||
|
self.name = DirectLabel(
|
||
|
text=label,
|
||
|
pos=(0, 0, offset),
|
||
|
**half_width_text_frame,
|
||
|
)
|
||
|
self.name = DirectLabel(
|
||
|
text=text,
|
||
|
pos=(canvas_width / 2, 0, offset),
|
||
|
**half_width_text_frame,
|
||
|
)
|
||
|
|
||
|
metadata = [
|
||
|
('Name', self.device.name),
|
||
|
('Device class', self.device.device_class.name),
|
||
|
('Manufacturer', self.device.manufacturer),
|
||
|
('USB ID',
|
||
|
"{:04x}:{:04x}".format(
|
||
|
self.device.vendor_id,
|
||
|
self.device.product_id,
|
||
|
),
|
||
|
),
|
||
|
]
|
||
|
for label, text in metadata:
|
||
|
add_data_entry(offset, label, text)
|
||
|
offset -= 0.1
|
||
|
|
||
|
# Axes
|
||
|
|
||
|
self.axis_sliders = []
|
||
|
if len(self.device.axes) > 0:
|
||
|
offset -= 0.1
|
||
|
self.axes_header = DirectLabel(
|
||
|
text="Axes",
|
||
|
pos=(0, 0, offset),
|
||
|
**header,
|
||
|
)
|
||
|
offset -= 0.1
|
||
|
|
||
|
def add_axis(offset, axis_name):
|
||
|
slider_width = canvas_width / 2
|
||
|
label = DirectLabel(
|
||
|
text=axis_name,
|
||
|
**left_aligned_small_text,
|
||
|
pos=(0.05, 0, offset),
|
||
|
parent=panel_canvas,
|
||
|
)
|
||
|
slider = DirectSlider(
|
||
|
value=0.0,
|
||
|
range=(-1.0, 1.0),
|
||
|
state=DGG.DISABLED,
|
||
|
frameSize=VBase4(
|
||
|
0,
|
||
|
slider_width,
|
||
|
-0.1,
|
||
|
0,
|
||
|
),
|
||
|
thumb_frameSize=VBase4(
|
||
|
0.0,
|
||
|
0.04,
|
||
|
-0.04,
|
||
|
0.04),
|
||
|
frameColor=VBase4(0.3, 0.3, 0.3, 1),
|
||
|
pos=(canvas_width - slider_width, 0, offset),
|
||
|
parent=panel_canvas,
|
||
|
)
|
||
|
return slider
|
||
|
|
||
|
for axis in self.device.axes:
|
||
|
axis_slider = add_axis(offset, axis.axis.name)
|
||
|
self.axis_sliders.append(axis_slider)
|
||
|
offset -= 0.1
|
||
|
|
||
|
# Buttons
|
||
|
|
||
|
self.button_buttons = []
|
||
|
if len(self.device.buttons) > 0:
|
||
|
offset -= 0.1
|
||
|
self.buttons_header = DirectLabel(
|
||
|
text="Buttons",
|
||
|
pos=(0, 0, offset),
|
||
|
**header,
|
||
|
)
|
||
|
offset -= 0.1
|
||
|
|
||
|
def add_button(offset, button_name):
|
||
|
button_width = canvas_width / 2
|
||
|
label = DirectLabel(
|
||
|
text=button_name,
|
||
|
**left_aligned_small_text,
|
||
|
pos=(0.05, 0, offset),
|
||
|
parent=panel_canvas,
|
||
|
)
|
||
|
button = DirectFrame(
|
||
|
frameSize=VBase4(
|
||
|
0,
|
||
|
button_width,
|
||
|
-0.1,
|
||
|
0,
|
||
|
),
|
||
|
text="",
|
||
|
text_align=TextNode.ACenter,
|
||
|
text_scale=0.05,
|
||
|
text_fg=VBase4(0,0,0,1),
|
||
|
text_pos=(button_width / 2, -0.06),
|
||
|
frameColor=VBase4(0.3, 0.3, 0.3, 1),
|
||
|
pos=(canvas_width - button_width, 0, offset),
|
||
|
parent=panel_canvas,
|
||
|
)
|
||
|
return button
|
||
|
|
||
|
for i in range(len(self.device.buttons)):
|
||
|
button_name = self.device.buttons[i].handle.name
|
||
|
button_button = add_button(offset, button_name)
|
||
|
self.button_buttons.append(button_button)
|
||
|
offset -= 0.1
|
||
|
|
||
|
# Vibration
|
||
|
|
||
|
self.vibration = []
|
||
|
if self.device.has_feature(InputDevice.Feature.vibration):
|
||
|
offset -= 0.1
|
||
|
self.vibration_header = DirectLabel(
|
||
|
text="Vibration",
|
||
|
pos=(0, 0, offset),
|
||
|
**header,
|
||
|
)
|
||
|
offset -= 0.1
|
||
|
|
||
|
def add_vibration(offset, axis_name, index):
|
||
|
slider_width = canvas_width / 2
|
||
|
label = DirectLabel(
|
||
|
text=axis_name,
|
||
|
**left_aligned_small_text,
|
||
|
pos=(0.05, 0, offset),
|
||
|
parent=panel_canvas,
|
||
|
)
|
||
|
slider = DirectSlider(
|
||
|
value=0.0,
|
||
|
range=(0.0, 1.0),
|
||
|
command=self.update_vibration,
|
||
|
frameSize=VBase4(
|
||
|
0,
|
||
|
slider_width,
|
||
|
-0.1,
|
||
|
0,
|
||
|
),
|
||
|
thumb_frameSize=VBase4(
|
||
|
0.0,
|
||
|
0.04,
|
||
|
-0.04,
|
||
|
0.04),
|
||
|
frameColor=VBase4(0.3, 0.3, 0.3, 1),
|
||
|
pos=(canvas_width - slider_width, 0, offset),
|
||
|
parent=panel_canvas,
|
||
|
)
|
||
|
return slider
|
||
|
|
||
|
for index, name in enumerate(["low frequency", "high frequency"]):
|
||
|
self.vibration.append(add_vibration(offset, name, index))
|
||
|
offset -= 0.1
|
||
|
|
||
|
# Resize the panel's canvas to the widgets actually in it.
|
||
|
if -offset > -canvas_height:
|
||
|
self.panel['canvasSize'] = VBase4(
|
||
|
0,
|
||
|
canvas_width,
|
||
|
offset,
|
||
|
0,
|
||
|
)
|
||
|
self.panel.setCanvasSize()
|
||
|
|
||
|
def show(self):
|
||
|
# FIXME: Activate update task here, and deactivate it in hide()?
|
||
|
self.panel.show()
|
||
|
|
||
|
def hide(self):
|
||
|
self.panel.hide()
|
||
|
|
||
|
def update_vibration(self):
|
||
|
low = self.vibration[0]['value']
|
||
|
high = self.vibration[1]['value']
|
||
|
self.device.set_vibration(low, high)
|
||
|
|
||
|
def update(self, task):
|
||
|
# FIXME: There needs to be a demo of events here, too.
|
||
|
for idx, slider in enumerate(self.axis_sliders):
|
||
|
slider["value"] = self.device.axes[idx].value
|
||
|
for idx, button in enumerate(self.button_buttons):
|
||
|
if self.device.buttons[idx].known:
|
||
|
if self.device.buttons[idx].pressed:
|
||
|
button['frameColor'] = VBase4(0.0, 0.8, 0.0, 1)
|
||
|
button['text'] = "down"
|
||
|
else:
|
||
|
button['frameColor'] = VBase4(0.3, 0.3, 0.3, 1)
|
||
|
button['text'] = "up"
|
||
|
else:
|
||
|
# State is InputDevice.S_unknown. This happens if the device
|
||
|
# manager hasn't polled yet, and in some cases before a button
|
||
|
# has been pressed after the program's start.
|
||
|
button['frameColor'] = VBase4(0.8, 0.8, 0.0, 1)
|
||
|
button['text'] = "unknown"
|
||
|
return task.cont
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
main = Main()
|
||
|
main.run()
|