| # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| ''' A module verifying whether X events satisfy specified criteria ''' |
| |
| import logging |
| import math |
| import os |
| import re |
| import time |
| import utils |
| |
| import trackpad_device |
| import trackpad_util |
| |
| from operator import le, ge, eq, lt, gt, ne, and_ |
| |
| |
| class XButton: |
| ''' Manipulation of X Button labels and values ''' |
| |
| # Define some common X button values |
| Left = 1 |
| Middle = 2 |
| Right = 3 |
| Wheel_Up = 4 |
| Wheel_Down = 5 |
| Wheel_Left = 6 |
| Wheel_Right = 7 |
| Mouse_Wheel_Up = 8 |
| Mouse_Wheel_Down = 9 |
| |
| def __init__(self): |
| self.display_environ = trackpad_util.Display().get_environ() |
| self.xinput_list_cmd = ' '.join([self.display_environ, 'xinput --list']) |
| self.xinput_dev_cmd = ' '.join([self.display_environ, |
| 'xinput --list --long %s']) |
| self.trackpad_dev_id = self._get_trackpad_dev_id() |
| |
| def _get_trackpad_dev_id(self): |
| trackpad_dev_id = None |
| if os.system('which xinput') == 0: |
| input_dev_str = utils.system_output(self.xinput_list_cmd) |
| for dev_str in input_dev_str.splitlines(): |
| res = re.search(r'(t(ouch|rack)pad\s+id=)(\d+)', dev_str, re.I) |
| if res is not None: |
| trackpad_dev_id = res.group(3) |
| break |
| return trackpad_dev_id |
| |
| def get_supported_buttons(self): |
| ''' Get supported button labels from xinput |
| |
| a device returned from 'xinput --list' looks like: |
| | SynPS/2 Synaptics TouchPad id=11 [slave pointer (2)] |
| |
| Button labels returned from 'xinput --list <device_id>' looks like: |
| Button labels: Button Left Button Middle Button Right Button Wheel Up |
| Button Wheel Down Button Horiz Wheel Left Button Horiz Wheel Right |
| Button 0 Button 1 Button 2 Button 3 Button 4 Button 5 Button 6 |
| Button 7 |
| ''' |
| DEFAULT_BUTTON_LABELS = ( |
| 'Button Left', 'Button Middle', 'Button Right', |
| 'Button Wheel Up', 'Button Wheel Down', |
| 'Button Horiz Wheel Left', 'Button Horiz Wheel Right', |
| 'Button 0', 'Button 1', 'Button 2', 'Button 3', |
| 'Button 4', 'Button 5', 'Button 6', 'Button 7') |
| |
| if self.trackpad_dev_id is not None: |
| features = utils.system_output(self.xinput_dev_cmd % |
| self.trackpad_dev_id) |
| button_labels_str = [line for line in features.splitlines() |
| if line.lstrip().startswith('Button labels:')] |
| strip_str = button_labels_str[0].lstrip().lstrip('Button labels:') |
| self.button_labels = tuple(['Button ' + b.strip() for b in |
| strip_str.split('Button') |
| if len(b) > 0]) |
| else: |
| logging.warn('Cannot find trackpad device in xinput. ' |
| 'Using default Button Labels instead.') |
| self.button_labels = DEFAULT_BUTTON_LABELS |
| logging.info('Button Labels (%d) in the trackpad: %s' % |
| (len(self.button_labels), self.button_labels)) |
| |
| return self.button_labels |
| |
| def get_value(self, button_label): |
| ''' Mapping an X button label to an X button value |
| |
| For example, 'Button Left' returns 1 |
| 'Button Wheel Up' returns 4 |
| 'Button Wheel Down' returns 5 |
| ''' |
| return self.button_labels.index(button_label) + 1 |
| |
| def get_label(self, button_value): |
| ''' Mapping an X button value to an X button label |
| |
| For example, 1 returns 'Button Left' |
| 4 returns 'Button Wheel Up' |
| 5 returns 'Button Wheel Down' |
| ''' |
| return self.button_labels[button_value - 1] |
| |
| def get_index(self, button_label): |
| ''' Mapping an X button label to its index in a button tuple |
| |
| Generally, a button index is equal to its button value decreased by 1. |
| For example, 'Button Left' returns 0 |
| 'Button Wheel Up' returns 3 |
| 'Button Wheel Down' returns 4 |
| ''' |
| return self.button_labels.index(button_label) |
| |
| def init_button_struct(self, value): |
| ''' Initialize a button dictionary to the given values. ''' |
| return dict(map(lambda b: (self.get_value(b), value), |
| self.button_labels)) |
| |
| |
| class Xcheck: |
| ''' Check whether X events observe test criteria ''' |
| RESULT_STR = {True : 'Pass', False : 'Fail'} |
| |
| def __init__(self, dev): |
| self.dev = dev |
| self.xevent_data = None |
| self.xbutton = XButton() |
| self.button_labels = self.xbutton.get_supported_buttons() |
| # Create a dictionary to look up button label |
| # e.g., {1: 'Button Left', ...} |
| self.button_dict = dict(map(lambda b: |
| (self.xbutton.get_value(b), b), |
| self.button_labels)) |
| self._get_boot_time() |
| |
| def _get_boot_time(self): |
| ''' Get the system boot up time |
| |
| Boot time can be used to convert the elapsed time since booting up |
| to that since Epoch. |
| ''' |
| stat_cmd = 'cat /proc/stat' |
| stat = utils.system_output(stat_cmd) |
| boot_time_tuple = tuple(int(line.split()[1]) |
| for line in stat.splitlines() |
| if line.startswith('btime')) |
| if len(boot_time_tuple) == 0: |
| raise error.TestError('Fail to extract boot time by "%s"' % |
| stat_cmd) |
| self.boot_time = boot_time_tuple[0] |
| |
| def _calc_distance(self, x0, y0, x1, y1): |
| ''' A simple Manhattan distance ''' |
| delta_x = abs(x1 - x0) |
| delta_y = abs(y1 - y0) |
| dist = round(math.sqrt(delta_x * delta_x + delta_y * delta_y)) |
| return [dist, [delta_x, delta_y]] |
| |
| def _parse_button_and_motion(self): |
| ''' Parse button events and motion events |
| |
| The variable seg_move accumulates the motions of the contiguous events |
| segmented by some boundary events such as Button events and other |
| NOP events. |
| A NOP (no operation) event is a fake X event which is |
| used to indicate the occurrence of some related device events. |
| ''' |
| |
| # Define some functions for seg_move |
| def _reset_seg_move(): |
| return [0, [0, 0]] |
| |
| def _append_motion_event(seg_move): |
| self.event_seq.append(('Motion', (seg_move[0], |
| ('Motion_x', seg_move[1][0]), |
| ('Motion_y', seg_move[1][1])))) |
| |
| def _add_seg_move(seg_move, move): |
| list_add = lambda list1, list2: map(sum, zip(list1, list2)) |
| return [seg_move[0] + move[0], list_add(seg_move[1], move[1])] |
| |
| self.count_buttons = self.xbutton.init_button_struct(0) |
| self.count_buttons_press = self.xbutton.init_button_struct(0) |
| self.count_buttons_release = self.xbutton.init_button_struct(0) |
| self.button_states = self.xbutton.init_button_struct('ButtonRelease') |
| |
| pre_xy = [None, None] |
| self.event_seq = [] |
| seg_move = _reset_seg_move() |
| self.sum_move = 0 |
| |
| indent1 = ' ' * 8 |
| indent2 = ' ' * 14 |
| log_msg = {True: indent2 + '{0} (button %d)', |
| False: indent2 + '{0} mis-matched (button %d)'} |
| precede_state = {'ButtonPress': 'ButtonRelease', |
| 'ButtonRelease': 'ButtonPress',} |
| logging.info(indent1 + 'X button events detected:') |
| |
| for line in self.xevent_data: |
| event_name = line[0] |
| if event_name != 'NOP': |
| event_dict = line[1] |
| if event_dict.has_key('coord'): |
| event_coord = list(eval(event_dict['coord'])) |
| if event_dict.has_key('button'): |
| event_button = eval(event_dict['button']) |
| |
| if event_name == 'EnterNotify': |
| if pre_xy == [None, None]: |
| pre_xy = event_coord |
| elif event_name == 'MotionNotify': |
| if pre_xy == [None, None]: |
| pre_xy = event_coord |
| else: |
| cur_xy = event_coord |
| move = self._calc_distance(*(pre_xy + cur_xy)) |
| pre_xy = cur_xy |
| seg_move = _add_seg_move(seg_move, move) |
| self.sum_move += move[0] |
| elif event_name.startswith('Button'): |
| _append_motion_event(seg_move) |
| seg_move = _reset_seg_move() |
| button_label = self.xbutton.get_label(event_button) |
| self.event_seq.append((event_name, button_label)) |
| prev_button_state = self.button_states[event_button] |
| self.button_states[event_button] = event_name |
| # A ButtonRelease should precede ButtonPress |
| # A ButtonPress should precede ButtonRelease |
| precede_flag = prev_button_state == precede_state[event_name] |
| if event_name == 'ButtonPress': |
| self.count_buttons_press[event_button] += 1 |
| elif event_name == 'ButtonRelease': |
| self.count_buttons_release[event_button] += 1 |
| self.count_buttons[event_button] += precede_flag |
| logging.info(log_msg[precede_flag].format(event_name) % |
| event_button) |
| elif event_name == 'NOP': |
| _append_motion_event(seg_move) |
| seg_move = _reset_seg_move() |
| self.event_seq.append(('NOP', line[1])) |
| |
| _append_motion_event(seg_move) |
| |
| # Convert dictionary to tuple |
| self.button_states = tuple(self.button_states.values()) |
| self.count_buttons= tuple(self.count_buttons.values()) |
| |
| def _set_flags(self): |
| ''' Set all flags to True before invoking check function ''' |
| self.motion_flag = True |
| self.button_flag = True |
| self.seq_flag = True |
| self.delay_flag = True |
| |
| def _get_result(self): |
| ''' Get the final result from various check flags ''' |
| flags = (self.motion_flag, self.button_flag, self.seq_flag, |
| self.delay_flag) |
| self.result = flags[0] if len(flags) == 1 else reduce(and_, flags) |
| logging.info(' --> Result: %s' % Xcheck.RESULT_STR[self.result]) |
| |
| def _compare(self, ops): |
| ''' Compare function generator |
| |
| Generate a function to compare two sequences using the specified |
| operators. |
| ''' |
| return lambda seq1, seq2: reduce(and_, map(lambda op, s1, s2: |
| op(s1, s2), ops, seq1, seq2)) |
| |
| def _button_criteria(self, button): |
| ''' Convert the key of the button tuple from label to index ''' |
| crit_button_count = [0,] * len(self.button_labels) |
| if button is not None: |
| button_label, button_value = button |
| button_index = self.xbutton.get_index(button_label) |
| crit_button_count[button_index] = button_value |
| return tuple(crit_button_count) |
| |
| def _insert_nop(self, nop_str): |
| ''' Insert a 'NOP' fake event into the xevent_data |
| |
| NOP is not an X event. It is inserted to indicate the occurrence of |
| related device events. |
| ''' |
| lifted_time = self.dev.get_2nd_finger_lifted_time() |
| if lifted_time is None: |
| logging.warn('Cannot get time for %s.' % nop_str) |
| else: |
| for index, line in enumerate(self.xevent_data): |
| event_name = line[0] |
| event_dict = line[1] |
| if event_name == 'MotionNotify': |
| event_time = float(event_dict['time']) |
| if event_time > lifted_time: |
| self.xevent_data.insert(index, ('NOP', nop_str)) |
| break |
| |
| def _get_direction(self): |
| directions = ['left', 'right', 'up', 'down'] |
| file_name = self.gesture_file_name.split('-')[self.func_name_pos] |
| for d in directions: |
| if d in file_name: |
| return d |
| return None |
| |
| ''' _verify_xxx() |
| Generic verification methods for various functionalities / areas |
| ''' |
| |
| def _verify_motion(self, compare, crit_max_movement): |
| ''' Verify if the observed motions satisfy the criteria ''' |
| self.motion_flag = compare(self.sum_move, crit_max_movement) |
| logging.info(' Verify motion: (%s)' % |
| Xcheck.RESULT_STR[self.motion_flag]) |
| logging.info(' Total movement = %d' % self.sum_move) |
| |
| def _verify_button(self, compare, crit_button_count): |
| ''' Verify if the observed buttons satisfy the criteria ''' |
| count_flag = compare(self.count_buttons, crit_button_count) |
| state_flags = map(lambda s: s == 'ButtonRelease', self.button_states) |
| state_flag = reduce(and_, state_flags) |
| self.button_flag = state_flag and count_flag |
| |
| logging.info(' Verify button: (%s)' % |
| Xcheck.RESULT_STR[self.button_flag]) |
| button_msg_details = ' %s %d times' |
| count_flag = False |
| for idx, b in enumerate(self.button_labels): |
| if self.count_buttons[idx] > 0: |
| logging.info(button_msg_details % (b, self.count_buttons[idx])) |
| count_flag = True |
| if not count_flag: |
| logging.info(' No Button events detected.') |
| |
| |
| def _verify_select_delay(self, crit_delay): |
| ''' Verify if the delay time satisfy the criteria |
| |
| The delay time is defined to be the time interval between the time |
| the 2nd finger touching the trackpad and the time of the corresponding |
| X Motion event. |
| ''' |
| # Extract scroll direction, i.e., 'up' or 'down', from the file name |
| # We do not support scrolling 'left' or 'right' at this time. |
| pos = self.func_name_pos |
| direction = self._get_direction() |
| |
| # Derive the device event playback time when the 2nd finger touches |
| dev_event_time = self.dev.get_2nd_finger_touch_time(direction) |
| |
| # Derive the motion event time of the 2nd finger |
| found_ButtonPress = False |
| event_time = None |
| for line in self.xevent_data: |
| event_name = line[0] |
| event_dict = line[1] |
| if not found_ButtonPress and event_name == 'ButtonPress': |
| found_ButtonPress = True |
| elif found_ButtonPress and event_name == 'MotionNotify': |
| event_time = float(event_dict['time']) |
| break |
| |
| if dev_event_time is None or event_time is None: |
| delay = 'Not found' |
| self.delay_flag = False |
| else: |
| delay = (event_time - dev_event_time) * 0.001 |
| self.delay_flag = delay < crit_delay |
| logging.info(' Verify delay: (%s)' % |
| Xcheck.RESULT_STR[self.delay_flag]) |
| logging.info(' Delay time = %s (criteria = %f)' % |
| (str(delay), crit_delay)) |
| |
| def _verify_select_sequence(self, crit_sequence): |
| ''' Verify event sequence against criteria sequence |
| |
| For example, the following event_sequence matches crit_sequence. |
| event_sequence: [('ButtonPress', 'Button Left'), |
| ('Motion', 68), |
| ('ButtonRelease', 'Button Left')] |
| crit_sequence: (('ButtonPress', 'Button Left'), |
| ('Motion', '>=', 20), |
| ('ButtonRelease', 'Button Left')) |
| ''' |
| op_dict = {'>=': ge, '<=': le, '==': eq, '=': eq, '>': gt, '<': lt, |
| '!=': ne, '~=': ne, 'not': ne, 'is not': ne} |
| op_le = op_dict['<='] |
| axis_dict = {'left': 'x', 'right': 'x', 'up': 'y', 'down': 'y', |
| None: ''} |
| self.seq_flag = True |
| crit_move_ratio = self.criteria.get('move_ratio', 0) |
| index = -1 |
| for e in self.event_seq: |
| e_type, e_value = e |
| fail_msg = None |
| index += 1 |
| if index >= len(crit_sequence): |
| fail_msg = 'Event (%s, %s) is extra compared to the criteria.' |
| fail_para = (e_type, str(e_value)) |
| break |
| crit_e = crit_sequence[index] |
| crit_e_type = crit_e[0] |
| |
| if e_type.startswith('Motion'): |
| motion_val = e_value[0] |
| motion_x_val = e_value[1][1] |
| motion_y_val = e_value[2][1] |
| if crit_e_type.startswith('Motion'): |
| crit_e_op = crit_e[1] |
| crit_e_val = crit_e[2] |
| op = op_dict[crit_e_op] |
| if crit_e_type == 'Motion': |
| crit_check = op(motion_val, crit_e_val) |
| if not crit_check: |
| fail_msg = '%s %s does not satisfy %s. ' |
| fail_para = (crit_e_type, str(e_value), str(crit_e)) |
| break |
| elif crit_e_type == 'Motion_x_or_y': |
| axis = axis_dict[self._get_direction()] |
| motion_axis_dict = {'x': {'this': motion_x_val, |
| 'other': motion_y_val}, |
| 'y': {'this': motion_y_val, |
| 'other': motion_x_val}} |
| motion_axis_val = motion_axis_dict[axis]['this'] |
| motion_other_val = motion_axis_dict[axis]['other'] |
| |
| check_this_axis = op(motion_axis_val, crit_e_val) |
| # If the criteria requests that one axis move more |
| # than a threshold value, the other axis should move |
| # much less. This is to confirm that the movement is |
| # in the right direction. |
| other_axis_cond = crit_e_op == '>=' or crit_e_op == '>' |
| bound_other_axis = motion_axis_val * crit_move_ratio |
| check_other_axis = (not other_axis_cond or |
| op_le(motion_other_val, bound_other_axis)) |
| crit_check = check_this_axis and check_other_axis |
| if not crit_check: |
| fail_msg = '%s %s does not satisfy %s. ' \ |
| 'Check motion for this axis = %s. ' \ |
| 'Check motion for the other axis = %s' |
| fail_para = (crit_e_type, str(e_value), str(crit_e), |
| check_this_axis, check_other_axis) |
| break |
| else: |
| fail_msg = '%s does not conform to the format.' |
| fail_para = crit_e_type |
| break |
| else: |
| # No motion allowed |
| if e_value > 0: |
| fail_msg = 'Motion %d is not allowed.' |
| fail_para = e_value |
| break |
| elif e_type == crit_e_type == 'ButtonPress' or \ |
| e_type == crit_e_type == 'ButtonRelease': |
| # Check if the button label matches criteria |
| if e_value != crit_e[1]: |
| fail_msg = 'Button %s does not match %s.' |
| fail_para = (e_value, crit_e[1]) |
| break |
| elif e_type == crit_e_type == 'NOP': |
| pass |
| else: |
| fail_msg = 'Event %s does not match criteria %s.' |
| fail_para = (e_type, crit_e_type) |
| break |
| |
| # Check if the criteria has been fully matched |
| if fail_msg is None and index < len(crit_sequence) - 1: |
| fail_msg = 'Some events are missing compared to the criteria: %s.' |
| fail_para = str(crit_sequence) |
| |
| if fail_msg is not None: |
| self.seq_flag = False |
| |
| logging.info(' Verify select sequence: (%s)' % |
| Xcheck.RESULT_STR[self.seq_flag]) |
| logging.info(' Detected event sequence') |
| for e in self.event_seq: |
| logging.info(' ' + str(e)) |
| if not self.seq_flag: |
| logging.info(' ' + fail_msg % fail_para) |
| |
| ''' _verify_area_xxx() |
| The following methods are generally used for the group of functionalities |
| in the same area. |
| ''' |
| |
| def _verify_all_criteria(self): |
| ''' A general verification method for all criteria ''' |
| self._parse_button_and_motion() |
| if self.criteria.has_key('max_movement'): |
| crit_max_movement = self.criteria['max_movement'] |
| self._verify_motion(le, crit_max_movement) |
| if self.criteria.has_key('button'): |
| crit_button_count = self._button_criteria(self.criteria['button']) |
| self._verify_button(eq, crit_button_count) |
| if self.criteria.has_key('delay'): |
| crit_delay = self.criteria['delay'] |
| self._verify_select_delay(crit_delay) |
| if self.criteria.has_key('sequence'): |
| crit_sequence = self.criteria['sequence'] |
| self._verify_select_sequence(crit_sequence) |
| self._get_result() |
| |
| ''' _check_xxx() |
| For each functionality xxx, there is a corresponding _check_xxx() method |
| which is executed by run() automatically. |
| ''' |
| |
| ''' area 0: 1 finger point & click ''' |
| |
| def _check_any_finger_click(self): |
| ''' Any finger, including thumb, can click ''' |
| self._verify_all_criteria() |
| |
| def _check_any_angle_click(self): |
| ''' Finger can be oriented at any angle relative to trackpad ''' |
| self._verify_all_criteria() |
| |
| def _check_any_location_click(self): |
| ''' Click can occur at any location on trackpad (no hot zones) ''' |
| self._verify_all_criteria() |
| |
| def _check_no_min_width_click(self): |
| ''' First finger should not have any minimum width defined for it |
| (i.e., point and/or click with finger tip. E.g., click with fingernail) |
| ''' |
| self._verify_all_criteria() |
| |
| def _check_no_cursor_wobble(self): |
| ''' No cursor wobble, creep, or jumping (or jump back) during clicking |
| ''' |
| self._verify_all_criteria() |
| |
| def _check_drum_roll(self): |
| ''' Drum roll: One finger (including thumb) touches trackpad followed |
| shortly (<500ms) by a second finger touching trackpad should not result |
| in cursor jumping |
| ''' |
| self._verify_all_criteria() |
| |
| ''' area 1: Click & select/drag ''' |
| |
| def _check_single_finger_select(self): |
| ''' (Single finger) Finger physical click or tap & a half, then finger - |
| remaining in contact with trackpad - drags along surface of trackpad |
| ''' |
| self._verify_all_criteria() |
| |
| def _check_single_finger_lifted(self): |
| ''' (Single finger) If finger leaves trackpad for only 800ms-1s |
| (Synaptics UX should know value), select/drag should continue |
| ''' |
| self._verify_all_criteria() |
| |
| def _check_two_fingers_select(self): |
| ''' (Two fingers) 1st finger click or tap & a half, 2nd finger's |
| movement selects/drags |
| ''' |
| self._verify_all_criteria() |
| |
| def _check_two_fingers_lifted(self): |
| ''' (Two fingers) Continues to drag when second finger is lifted then |
| placed again |
| ''' |
| self._insert_nop('2nd Finger Lifted') |
| self._verify_all_criteria() |
| |
| def _check_two_fingers_no_delay(self): |
| ''' (Two fingers) Drag should be immediate (no delay between movement |
| of finger and movement of selection/drag) |
| ''' |
| self._verify_all_criteria() |
| |
| ''' area 2: 2 finger alternate/right click ''' |
| |
| def _check_x_seconds_interval(self): |
| ''' 1st use case: 1st finger touches trackpad, X seconds pass, 2nd |
| finger touches trackpad, physical click = right click, where X is any |
| number of seconds |
| ''' |
| self._verify_all_criteria() |
| |
| def _check_roll_case(self): |
| ''' 2nd use case ("roll case"): If first finger generates a click and |
| second finger "rolls on" within 300ms of first finger click (again, |
| rely on Synaptics UX to know value), an alternate/right click results |
| ''' |
| self._verify_all_criteria() |
| |
| def _check_one_finger_tracking(self): |
| ''' 1 finger tracking, never leaves trackpad, 2f arrives and there is |
| a click. right click results. |
| ''' |
| self._verify_all_criteria() |
| |
| ''' area 3: 2 finger scroll ''' |
| |
| def _check_two_finger_scroll(self): |
| ''' Vertical scroll, reflecting movement of finger(s) |
| |
| Criteria: |
| 1. sum of movement is less than crit_max_movement |
| 2. if subname in the file name is up: |
| A number of button 4 (Wheel Up) events should be observed |
| without other button events. |
| elif subname in the file name is down: |
| A number of button 5 (Wheel Down) events should be observed |
| without other button events. |
| ''' |
| # Extract scroll direction, i.e., 'up' or 'down', from the file name |
| pos = self.func_name_pos |
| direction = self._get_direction() |
| |
| # Get criteria for max movement and wheel up/down |
| crit_max_movement = self.criteria['max_movement'] |
| ops = [eq,] * len(self.button_labels) |
| if direction == 'up': |
| button_label = self.xbutton.get_label(self.xbutton.Wheel_Up) |
| ops[self.xbutton.get_index(button_label)] = ge |
| crit_up = self.criteria['button'][0] |
| crit_button_count = self._button_criteria(crit_up) |
| elif direction == 'down': |
| button_label = self.xbutton.get_label(self.xbutton.Wheel_Down) |
| ops[self.xbutton.get_index(button_label)] = ge |
| crit_down = self.criteria['button'][1] |
| crit_button_count = self._button_criteria(crit_down) |
| else: |
| msg = ' scroll direction in the file name is not correct: (%s)' |
| logging.info(msg % direction) |
| self.result = False |
| return |
| |
| self._parse_button_and_motion() |
| self._verify_motion(le, crit_max_movement) |
| self._verify_button(self._compare(tuple(ops)), crit_button_count) |
| self._get_result() |
| |
| ''' area 4: Palm/thumb detection ''' |
| |
| ''' parse and run below ''' |
| |
| def _extract_prop(self, event_name, line, prop_key): |
| ''' Extract property from X events ''' |
| if line is None: |
| logging.warn(' X event format may not be correct.') |
| return None |
| |
| # Declare the format to extract information from X event structures |
| format_dict = { |
| 'Motion_coord' : '{6}', |
| 'Motion_time' : '{5}', |
| 'Motion_tv' : '{7}', |
| 'Button_coord' : '{6}', |
| 'Button_button' : '{3}', |
| 'Button_time' : '{5}', |
| 'Button_tv' : '{7}', |
| } |
| event_format_str = format_dict[event_name] |
| |
| try: |
| prop_val = event_format_str.format(*line.strip().split()).strip(',') |
| except IndexError, err: |
| logging.warn(' %s in X event data.' % str(err)) |
| return None |
| return (prop_key, prop_val) |
| |
| def _parse(self, xevent_str): |
| ''' Parse all X events |
| |
| The event information of a single X event may span across multiple |
| lines. This function extracts the important event information of |
| an event into a dictionary so that it is easier to process in |
| subsequent stages. |
| |
| For example: |
| A MotionNotify event looks like: |
| MotionNotify event, serial 25, synthetic NO, window 0xa00001, |
| root 0xab, subw 0x0, time 925196, (750,395), root:(750,395), |
| state 0x0, is_hint 0, same_screen YES |
| |
| A ButtonPress event looks like: |
| ButtonPress event, serial 25, synthetic NO, window 0xa00001, |
| root 0xab, subw 0x0, time 1098904, (770,422), root:(770,422), |
| state 0x0, button 1, same_screen YES |
| |
| The property extracted for the MotionNotify event looks like: |
| ['MotionNotify', {'coord': (150,200), 'time': ...] |
| |
| The property extracted for the ButtonPress event looks like: |
| ['ButtonPress', {'coord': (150,200), 'button': 5}, 'time': ...] |
| ''' |
| |
| if len(xevent_str) == 0: |
| logging.warn(' No X events were captured.') |
| return False |
| |
| xevent_iter = iter(xevent_str) |
| self.xevent_data = [] |
| while True: |
| line = next(xevent_iter, None) |
| if line is None: |
| break |
| line_words = line.split() |
| if len(line_words) > 0: |
| event_name = line_words[0] |
| else: |
| continue |
| |
| # Extract event information for important event types |
| if event_name == 'MotionNotify' or event_name == 'EnterNotify': |
| line1 = next(xevent_iter, None) |
| line2 = next(xevent_iter, None) |
| prop_coord = self._extract_prop('Motion_coord', line1, 'coord') |
| prop_time = self._extract_prop('Motion_time', line1, 'time') |
| if prop_coord is not None and prop_time is not None: |
| event_dict = dict([prop_coord, prop_time]) |
| self.xevent_data.append([event_name, event_dict]) |
| elif line.startswith('Button'): |
| line1 = next(xevent_iter, None) |
| line2 = next(xevent_iter, None) |
| prop_coord = self._extract_prop('Button_coord', line1, 'coord') |
| prop_time = self._extract_prop('Button_time', line1, 'time') |
| prop_button = self._extract_prop('Button_button', line2, |
| 'button') |
| if (prop_coord is not None and prop_button is not None |
| and prop_time is not None): |
| event_dict = dict([prop_coord, prop_button, prop_time]) |
| self.xevent_data.append([event_name, event_dict]) |
| return True |
| |
| def run(self, tp_func, tp_data, xevent_str): |
| ''' Parse the x events and invoke a proper check function |
| |
| Invoke the corresponding check function based on its functionality name. |
| For example, tp_func.name == 'no_cursor_wobble' will result in the |
| invocation of self._check_no_cursor_wobble() |
| ''' |
| parse_result = self._parse(xevent_str) |
| self.gesture_file_name = tp_data.file_basename |
| self.func_name_pos = 0 if tp_data.prefix is None else 1 |
| self.criteria = tp_func.criteria |
| if parse_result: |
| check_function = eval('self._check_' + tp_func.name) |
| self._set_flags() |
| check_function() |
| return self.result |
| else: |
| return False |