Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4dcbc98
A test case for randomly generate input events
GabrielChenCC Mar 6, 2025
45f2cb1
format files with black
GabrielChenCC Mar 6, 2025
ec60fac
rename the file name
GabrielChenCC Mar 6, 2025
7031c05
Add depends : python3-evdev
GabrielChenCC Mar 6, 2025
b8cfcd9
add python3-evdev
GabrielChenCC Mar 6, 2025
d593d33
back to packaging.pxu initial
GabrielChenCC Mar 6, 2025
e140764
Add depends python3-evdev
GabrielChenCC Mar 6, 2025
b9b3606
Remove the python3-evdev in packaging.pxu
GabrielChenCC Mar 7, 2025
1679bea
Try add the python3-evdev modual
GabrielChenCC Mar 7, 2025
ab83fe2
try add evdev in tox.ini
GabrielChenCC Mar 7, 2025
21314fa
Add evdev in setup.cfg
GabrielChenCC Mar 7, 2025
a712721
Add evdev in tox.ini for all python version
GabrielChenCC Mar 7, 2025
ff9436e
modify mk-venv
GabrielChenCC Mar 7, 2025
3fa9dbe
Correct the typo
GabrielChenCC Mar 7, 2025
5754511
Modify the test_mouse_key.py, correct the typo.
GabrielChenCC Mar 7, 2025
60c095b
fix black error
GabrielChenCC Mar 7, 2025
a92982c
modify test_mouse_key.py file
GabrielChenCC Mar 10, 2025
1db5a68
modify it again
GabrielChenCC Mar 10, 2025
aaf9b97
modify the py again again
GabrielChenCC Mar 10, 2025
1eb4b9d
fix python3.8 tox failed.
GabrielChenCC Mar 10, 2025
7bdcaff
adjust unittest
GabrielChenCC Apr 1, 2025
a1d3533
black
GabrielChenCC Apr 1, 2025
eaf41f7
flake8 test
GabrielChenCC Apr 1, 2025
f618faf
called_once() -> called_once_with()
GabrielChenCC Apr 1, 2025
ca893a6
test both mouse and keyborad
GabrielChenCC Apr 1, 2025
e063ad5
black it again
GabrielChenCC Apr 1, 2025
dbeb381
Modify the script according to pieqq's suggestion
GabrielChenCC May 7, 2025
63ca00d
black check
GabrielChenCC May 7, 2025
c8f8f46
adjust the script
GabrielChenCC May 7, 2025
71b0bdb
modify the line 76, make the value 2 to value 1
GabrielChenCC May 7, 2025
9e2d0c1
Fix divided by zero issue
GabrielChenCC Jul 11, 2025
98e8c7d
fix the bug 'DELTA + 1'
GabrielChenCC Jul 11, 2025
7a688ec
Add python3-evdev into packaging.pxu
GabrielChenCC Jul 18, 2025
515d494
add python3-evdev in packaging.pxu
GabrielChenCC Jul 18, 2025
f526c88
Try to add the python3-evdev in packaging.pxu
GabrielChenCC Jul 18, 2025
52ca2d0
add dependency in contral file
GabrielChenCC Jul 18, 2025
f43ebab
remove wrong text
GabrielChenCC Jul 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion checkbox-ng/mk-venv
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ virtualenv --quiet --system-site-packages --python=python3 "$venv_path"
# shellcheck source=/dev/null
. "$venv_path"/bin/activate
python3 -m pip install -e .
pip install tqdm psutil
pip install tqdm psutil evdev

mkdir -p "$venv_path/share/plainbox-providers-1"
echo "export PROVIDERPATH=$venv_path/share/plainbox-providers-1" >> "$venv_path"/bin/activate
Expand Down
161 changes: 161 additions & 0 deletions providers/base/bin/mouse_keyboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#!/usr/bin/env python3
#
# This file is part of Checkbox.
#
# Copyright 2025 Canonical Ltd.
#
# Authors:
# Gabriel Chen <[email protected]>
#
# Checkbox is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3,
# as published by the Free Software Foundation.
#
# Checkbox is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
"""mouse_key_random utility."""

import time
import random
from evdev import UInput, ecodes as e

# Constants
FREQUENCY_USEC = 100000 # Frequency of events in microseconds
N_EPISODES = 81 # Number of events to generate
WEIGHT_MOUSEMOVE = 10 # Weight of mouse movements
WEIGHT_KEYPRESS = 1 # Weight of key presses
WEIGHT_SUM = WEIGHT_MOUSEMOVE + WEIGHT_KEYPRESS # Total weight

MOVE_MAX = 100 # Maximum mouse movement distance
MOVE_DELTA = 5 # Mouse movement step size


# Define keyboard keys and mouse buttons
KEYBOARD_KEYS = [
e.KEY_A,
e.KEY_B,
e.KEY_C,
e.KEY_D,
e.KEY_E,
e.KEY_F,
e.KEY_G,
e.KEY_H,
e.KEY_I,
e.KEY_J,
e.KEY_K,
e.KEY_L,
e.KEY_M,
e.KEY_N,
e.KEY_O,
e.KEY_P,
e.KEY_Q,
e.KEY_R,
e.KEY_S,
e.KEY_T,
e.KEY_U,
e.KEY_V,
e.KEY_W,
e.KEY_X,
e.KEY_Y,
e.KEY_Z,
e.KEY_1,
e.KEY_2,
e.KEY_3,
e.KEY_4,
e.KEY_5,
e.KEY_6,
e.KEY_7,
e.KEY_8,
e.KEY_9,
e.KEY_0,
]
MOUSE_BUTTONS = [e.BTN_LEFT, e.BTN_RIGHT]


# Initialize the virtual input device
def dev_init(name):
# Define the capabilities of the device (keyboard and mouse events)
capabilities = {
e.EV_KEY: KEYBOARD_KEYS + MOUSE_BUTTONS,
e.EV_REL: [e.REL_X, e.REL_Y],
}
# Create the virtual input device
device = UInput(
capabilities, name=name, vendor=0xBAD, product=0xA55, version=777
)
time.sleep(1) # Give userspace time to detect the new device
return device


# Destroy the virtual input device
def dev_deinit(device):
time.sleep(1) # Give userspace time to read the remaining events
device.close()


# Simulate a key press
def key_press(device, key):
device.write(e.EV_KEY, key, 1) # Press the key
device.write(e.EV_KEY, key, 0) # Release the key
device.syn() # Synchronize the event


# Simulate mouse movement
def mouse_move(device, x, y):
device.write(e.EV_REL, e.REL_X, x) # Move mouse on the X axis
device.write(e.EV_REL, e.REL_Y, y) # Move mouse on the Y axis
device.syn() # Synchronize the event


# Randomly press a key
def rand_key_press(device):
key = random.choice(KEYBOARD_KEYS) # Choose a random key
key_press(device, key) # Simulate the key press
time.sleep(FREQUENCY_USEC / 1000000.0) # Wait for the defined frequency


# Randomly move the mouse
def rand_mouse_moves(device):
# Generate random X and Y movements
x = random.randint(-MOVE_MAX // 2, MOVE_MAX // 2)
y = random.randint(-MOVE_MAX // 2, MOVE_MAX // 2)
steps = (
max(abs(x), abs(y)) // MOVE_DELTA + 1
) # Calculate the number of steps
# Move the mouse in small steps for smooth movement
for _ in range(steps):
mouse_move(device, x // steps, y // steps)
time.sleep(FREQUENCY_USEC / 1000000.0 / MOVE_DELTA)

# Handle any remaining movement
rest_x = x % steps
rest_y = y % steps
if rest_x or rest_y:
mouse_move(device, rest_x, rest_y)
time.sleep(FREQUENCY_USEC / 1000000.0 / MOVE_DELTA)


def main():
# Initialize the virtual input device
device = dev_init("umad")
random.seed(time.time()) # Seed the random number generator

# Generate random events
for _ in range(N_EPISODES):
action = random.randint(0, WEIGHT_SUM - 1) # Choose a random action
if action < WEIGHT_MOUSEMOVE:
rand_mouse_moves(device) # Simulate mouse movement
else:
rand_key_press(device) # Simulate key press

# Destroy the virtual input device
dev_deinit(device)


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions providers/base/debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Recommends: bonnie++,
pm-utils,
python3-apt,
python3-dbus,
python3-evdev,
python3-gi,
smartmontools,
sysstat,
Expand Down
160 changes: 160 additions & 0 deletions providers/base/tests/test_mouse_keyboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#!/usr/bin/env python3
import unittest
from unittest.mock import MagicMock, patch
import time
import random
import evdev
from evdev import ecodes as e
from mouse_keyboard import (
dev_init,
dev_deinit,
key_press,
mouse_move,
rand_key_press,
rand_mouse_moves,
main,
KEYBOARD_KEYS,
MOUSE_BUTTONS,
FREQUENCY_USEC,
MOVE_MAX,
MOVE_DELTA,
WEIGHT_MOUSEMOVE,
WEIGHT_KEYPRESS,
WEIGHT_SUM,
N_EPISODES,
)


class TestMouseKeyboard(unittest.TestCase):

@patch("mouse_keyboard.UInput")
@patch("mouse_keyboard.time.sleep")
def test_dev_init(self, mock_sleep, mock_uinput):
# Mock the UInput class
mock_device = MagicMock()
mock_uinput.return_value = mock_device

# Call the function
device = dev_init("umad")

# Assertions
mock_uinput.assert_called_once_with(
{
e.EV_KEY: KEYBOARD_KEYS + MOUSE_BUTTONS,
e.EV_REL: [e.REL_X, e.REL_Y],
},
name="umad",
vendor=0xBAD,
product=0xA55,
version=777,
)
self.assertEqual(device, mock_device)
mock_sleep.assert_called_once_with(1)

@patch("mouse_keyboard.time.sleep")
def test_dev_deinit(self, mock_sleep):
# Mock the device
mock_device = MagicMock()

# Call the function
dev_deinit(mock_device)

# Assertions
mock_sleep.assert_called_once_with(1)
mock_device.close.assert_called_once_with()

def test_key_press(self):
# Mock the device
mock_device = MagicMock()

# Call the function
key_press(mock_device, e.KEY_A)

# Assertions
mock_device.write.assert_any_call(e.EV_KEY, e.KEY_A, 1)
mock_device.write.assert_any_call(e.EV_KEY, e.KEY_A, 0)
self.assertEqual(mock_device.syn.call_count, 1)

def test_mouse_move(self):
# Mock the device
mock_device = MagicMock()

# Call the function
mouse_move(mock_device, 10, 20)

# Assertions
mock_device.write.assert_any_call(e.EV_REL, e.REL_X, 10)
mock_device.write.assert_any_call(e.EV_REL, e.REL_Y, 20)
mock_device.syn.assert_called_once_with()

@patch("mouse_keyboard.random.choice")
@patch("mouse_keyboard.time.sleep")
def test_rand_key_press(self, mock_sleep, mock_choice):
# Mock the device and random choice
mock_device = MagicMock()
mock_choice.return_value = e.KEY_B

# Call the function
rand_key_press(mock_device)

# Assertions
mock_choice.assert_called_once_with(KEYBOARD_KEYS)
mock_device.write.assert_any_call(e.EV_KEY, e.KEY_B, 1)
mock_device.write.assert_any_call(e.EV_KEY, e.KEY_B, 0)
mock_sleep.assert_called_once_with(FREQUENCY_USEC / 1000000.0)

@patch("mouse_keyboard.random.randint")
@patch("mouse_keyboard.time.sleep")
def test_rand_mouse_moves(self, mock_sleep, mock_randint):
# Mock the device and random.randint
mock_device = MagicMock()
mock_randint.side_effect = [50, -30] # x, y

# Call the function
rand_mouse_moves(mock_device)

# Assertions
self.assertEqual(mock_randint.call_count, 2)
mock_device.write.assert_any_call(
e.EV_REL, e.REL_X, 50 // (50 // MOVE_DELTA + 1)
)
mock_device.write.assert_any_call(
e.EV_REL, e.REL_Y, -30 // (50 // MOVE_DELTA + 1)
)
self.assertGreaterEqual(mock_sleep.call_count, 1)

@patch("mouse_keyboard.time.time")
@patch("mouse_keyboard.rand_mouse_moves")
@patch("mouse_keyboard.random.seed")
@patch("mouse_keyboard.random.randint")
@patch("mouse_keyboard.dev_init")
@patch("mouse_keyboard.dev_deinit")
def test_main(
self,
mock_dev_deinit,
mock_dev_init,
mock_randint,
mock_seed,
mock_rand_mouse_moves,
mock_time,
):
# Mock the device and random functions
mock_device = MagicMock()
mock_dev_init.return_value = mock_device
mock_randint.side_effect = [
0,
10,
] * N_EPISODES # Always choose mouse movement

# Call the function
main()

# Assertions
mock_seed.assert_called_once_with(mock_time())
mock_dev_init.assert_called_once_with("umad")
self.assertEqual(mock_randint.call_count, N_EPISODES)
mock_dev_deinit.assert_called_once_with(mock_device)


if __name__ == "__main__":
unittest.main()
5 changes: 5 additions & 0 deletions providers/base/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ commands =
[testenv:py35]
deps =
flake8
evdev
coverage == 5.5
distro == 1.0.1
Jinja2 == 2.8
Expand All @@ -42,6 +43,7 @@ setenv=
[testenv:py36]
deps =
flake8
evdev
coverage == 5.5
distro == 1.0.1
Jinja2 == 2.10
Expand All @@ -60,6 +62,7 @@ deps =
[testenv:py38]
deps =
flake8
evdev
coverage == 7.3.0
distro == 1.4.0
Jinja2 == 2.10.1
Expand All @@ -77,6 +80,7 @@ deps =
[testenv:py310]
deps =
flake8
evdev
coverage == 7.3.0
distro == 1.7.0
Jinja2 == 3.0.3
Expand All @@ -95,6 +99,7 @@ deps =
[testenv:py312]
deps =
flake8
evdev
coverage == 7.4.4
distro == 1.9.0
Jinja2 == 3.1.2
Expand Down
2 changes: 1 addition & 1 deletion providers/base/units/graphics/packaging.pxu
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ Depends: gnome-randr

unit: packaging meta-data
os-id: debian
Depends: gnome-screenshot
Depends: gnome-screenshot
Loading