forked from ISO-B/pmb-pitft
-
Notifications
You must be signed in to change notification settings - Fork 1
/
ui.py
378 lines (319 loc) · 13.6 KB
/
ui.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
#!/usr/bin/python2
# -*- coding: utf-8 -*-
import sys, pygame
from pygame.locals import *
import time
import os
import subprocess
import logging
import datetime
from math import ceil, floor
from datetime import timedelta
from signal import alarm, signal, SIGALRM, SIGTERM, SIGKILL
from logging.handlers import TimedRotatingFileHandler
from daemon import Daemon
# Own modules
from control import PlayerControl
from screen_manager import ScreenManager
import config
# Additional modules, if in config
if config.lircrcfile:
import lirc
# OS enviroment variables for pitft
#os.putenv ("SDL_VIDEODRIVER" , "fbcon")
os.environ["SDL_FBDEV"] = "/dev/fb1"
os.environ["SDL_MOUSEDEV"] = "/dev/input/touchscreen"
os.environ["SDL_MOUSEDRV"] = "TSLIB"
# Logger config
if not os.path.isdir (config.logpath):
os.mkdir(config.logpath)
path = os.path.dirname(os.path.abspath(__file__)) + "/"
logger = logging.getLogger("PiTFT-Playerui")
try:
if config.loglevel == "DEBUG":
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s %(levelname)-5s %(name)-32s %(lineno)-4d %(message)s")
else:
logger.setLevel(logging.INFO)
formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
except:
logger.setLevel(logging.INFO)
handler = TimedRotatingFileHandler(config.logpath + '/pitft-playerui.log',when="midnight",interval=1,backupCount=14)
handler.setFormatter(formatter)
logger.addHandler(handler)
## HAX FOR FREEZING ##
class Alarm(Exception):
pass
def alarm_handler(signum, frame):
logger.debug("ALARM")
raise Alarm
## HAX END ##
def signal_term_handler(signal, frame):
logger.debug('got SIGTERM')
sys.exit(0)
class PitftDaemon(Daemon):
# Setup Python game and Screen manager
def setup(self):
logger.info("Starting setup")
signal(SIGTERM, signal_term_handler)
# Python game ######################
logger.info("Setting pygame")
pygame_init_done = False
while not pygame_init_done:
try:
pygame.init()
pygame_init_done = True
except:
logger.debug("Pygame init failed")
pygame_init_done = False
time.sleep(5)
pygame.mouse.set_visible(False)
# Hax for freezing
signal(SIGALRM, alarm_handler)
alarm(1)
try:
# Set screen size
size = width, height = config.resolution
self.screen = pygame.display.set_mode(size)
alarm(0)
except Alarm:
logger.debug("Keyboard interrupt?")
raise KeyboardInterrupt
# Hax end
logger.info("Display driver: %s" % pygame.display.get_driver())
# Player control ###############
logger.info("Setting player control")
self.pc = PlayerControl()
logger.debug("Player control set")
# Screen manager ###############
logger.info("Setting screen manager")
self.sm = ScreenManager(path, self.pc)
logger.debug("Screen manager set")
# LIRC
lircrcfile = path + config.lircrcfile
self.lirc_enabled = False
if os.path.isfile(lircrcfile):
try:
self.lirc_sockid = lirc.init("pitft-playerui", lircrcfile, blocking=False)
self.lirc_enabled = True
except Exception as e:
logger.error(e)
self.lirc_enabled = False
# Mouse variables
self.clicktime = datetime.datetime.now()
self.longpress_time = timedelta(milliseconds=300)
self.click_filtertime = datetime.datetime.now()
self.click_filterdelta = timedelta(milliseconds=10)
self.scroll_threshold = (20, 20)
self.start_pos = 0,0
self.mouse_scroll = ""
self.mousebutton_down = False
self.pos = 0
# Smooth scrolling variables
self.smoothscroll = False
self.smoothscroll_direction = 0,0
self.smoothscroll_directions_index = 0
self.smoothscroll_direction_samples = 10
self.smoothscroll_directions = [0]*self.smoothscroll_direction_samples
self.smoothscroll_factor = 0.9
self.smoothscroll_timedelta = timedelta(milliseconds=1)
self.smoothscroll_time = datetime.datetime.now()
# Times in milliseconds
self.screen_refreshtime = 16.67
self.player_refreshtime = 200
#Backlight
self.screen_timer = 0.0
self.backlight = False
self.update_screen_timeout(True)
logger.debug("Setup done")
def shutdown(self):
pass
# Main loop
def run(self):
self.setup()
drawtime = datetime.datetime.now()
refreshtime = datetime.datetime.now()
while 1:
updated = False
# Check mouse and LIRC events
try:
active = self.read_mouse()
if self.lirc_enabled:
active = active | self.read_lirc()
except Exception as e:
logger.error(e)
try:
# Refresh info
if refreshtime < datetime.datetime.now():
refreshtime = datetime.datetime.now() + timedelta(milliseconds=self.player_refreshtime)
# Refresh information from players
ret, updated = self.pc.refresh()
active = active | ret
# Update screen
if updated:
self.sm.refresh()
except Exception as e:
logger.error(e)
try:
# Update screen timeout, if there was any activity
if config.screen_timeout > 0:
self.update_screen_timeout(active)
except Exception as e:
logger.error(e)
try:
# Draw screen
if drawtime < datetime.datetime.now():
drawtime = datetime.datetime.now() + timedelta(milliseconds=self.screen_refreshtime)
# Don't draw when display is off
if self.backlight:
self.sm.render(self.screen)
pygame.display.flip()
else:
time.sleep(0.01)
except Exception as e:
logger.error(e)
def read_mouse(self):
direction = 0,0
userevents = False
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
self.clicktime = datetime.datetime.now()
# Filter out if instantly after previous mousebutton up event
if self.clicktime > self.click_filtertime:
self.pos = self.start_pos = pygame.mouse.get_pos()
userevents = True
if event.button == 1:
if self.smoothscroll:
self.scroll(self.start_pos, (0,0), True)
self.smoothscroll = False
self.smoothscroll_directions_index = 0
self.smoothscroll_directions = [0]*self.smoothscroll_direction_samples
self.smoothscroll_direction = 0,0
# Instant click when backlight is off to wake
if not self.backlight:
self.mousebutton_down = False
else:
self.mousebutton_down = True
# Scroll wheel
elif event.button == 4:
self.scroll(self.start_pos, (0,30), True)
elif event.button == 5:
self.scroll(self.start_pos, (0,-30), True)
elif event.type == pygame.MOUSEMOTION and self.mousebutton_down:
userevents = True
pos = pygame.mouse.get_pos()
direction = (pos[0] - self.pos[0], pos[1] - self.pos[1])
if not self.mouse_scroll:
# Start scrolling: Lock direction
if abs(direction[0]) >= self.scroll_threshold[0]:
self.mouse_scroll = "x"
elif abs(direction[1]) >= self.scroll_threshold[1]:
self.mouse_scroll = "y"
# Scrolling already, update offset
if self.mouse_scroll == "x":
direction = direction[0], 0
elif self.mouse_scroll == "y":
direction = 0, direction[1]
else:
direction = 0, 0
if self.mouse_scroll:
self.smoothscroll = self.scroll(self.start_pos, direction)
# Save directions from latest samples for smooth scrolling - Direction is always Y
self.smoothscroll_directions_index = self.smoothscroll_directions_index + 1
if self.smoothscroll_directions_index > self.smoothscroll_direction_samples-1:
self.smoothscroll_directions_index = 0
self.smoothscroll_directions[self.smoothscroll_directions_index] = direction[1]
self.smoothscroll_direction = 0, sum(self.smoothscroll_directions)
# Save new position
self.pos = pos
elif event.type == pygame.MOUSEBUTTONUP and event.button == 1:
userevents = True
if self.mousebutton_down:
# Not a long click or scroll: click
if not self.mouse_scroll:
self.click(1, self.start_pos)
# Scrolling: End right away or start deceleration if allowed
else:
if self.smoothscroll:
self.scroll(self.start_pos, self.smoothscroll_direction)
self.smoothscroll_time = datetime.datetime.now() + self.smoothscroll_timedelta
else:
self.scroll(self.start_pos, (0,0), True)
self.mouse_scroll = ""
# Clear variables
self.mousebutton_down = False
# Filter next click, if it happens instantly
self.click_filtertime = datetime.datetime.now() + self.click_filterdelta
# Long press - register second click
if self.mousebutton_down and not self.mouse_scroll and not self.smoothscroll:
userevents = True
if datetime.datetime.now() - self.clicktime > self.longpress_time:
self.mousebutton_down = self.click(2, self.start_pos)
# Update timers
self.clicktime = datetime.datetime.now()
# No activity, but smooth scrolling
if self.smoothscroll and not self.mousebutton_down:
if datetime.datetime.now() > self.smoothscroll_time:
userevents = True
self.smoothscroll_direction = 0, int(self.smoothscroll_direction[1] * self.smoothscroll_factor)
# Decelerated under threshold -> Stop scrolling
if abs(self.smoothscroll_direction[1]) < self.scroll_threshold[1]:
self.scroll(self.start_pos, (0,0), True)
self.mouse_scroll = ""
self.smoothscroll = False
self.smoothscroll_directions_index = 0
self.smoothscroll_directions = [0]*self.smoothscroll_direction_samples
self.smoothscroll_direction = 0,0
else: # Continue scrolling
self.scroll(self.start_pos, self.smoothscroll_direction)
self.smoothscroll_time = datetime.datetime.now() + self.smoothscroll_timedelta
return userevents
def click(self, mousebutton, clickpos):
self.sm.click(mousebutton, clickpos)
def scroll(self, start, direction, end=False):
return self.sm.scroll(start, direction, end)
def read_lirc(self):
commands = lirc.nextcode()
if commands:
for line in commands:
logger.debug("LIRC: %s" % line)
try:
target, command = line.split()
if target == "switch":
self.sm.switch_player(command)
elif target == "control":
self.pc.control_player(command)
else:
logger.debug("LIRC: Unknown target %s" % target)
except Exception as e:
logger.error(e)
return True
return False
def set_backlight(self, state):
logger.debug("Backlight %s" %state)
subprocess.call("echo '" + str(state*1) + "' > " + config.backlight_sysfs, shell=True)
self.backlight = state
def update_screen_timeout(self, active):
if active:
self.screen_timer = datetime.datetime.now() + timedelta(seconds=config.screen_timeout)
if not self.backlight:
self.set_backlight(True)
elif self.screen_timer < datetime.datetime.now() and self.backlight:
self.set_backlight(False)
if __name__ == "__main__":
daemon = PitftDaemon('/tmp/pitft-playerui-daemon.pid')
if len(sys.argv) > 1:
if 'start' == sys.argv[1]:
daemon.start()
elif 'stop' == sys.argv[1]:
daemon.shutdown()
daemon.stop()
elif 'restart' == sys.argv[1]:
daemon.restart()
else:
print ("Unknown command")
sys.exit(2)
sys.exit(0)
else:
print ("usage: %s start|stop|restart" % sys.argv[0])
sys.exit(2)