#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# (c) Copyright 2003-2015 HP Development Company, L.P.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Author: Don Welch
#
__version__ = '12.0'
__title__ = "Services and Status System Tray dBus Child/Parent Process"
__mod__ = 'hpssd'
__doc__ = "Provides persistent data and event services to HPLIP client applications. Required to be running for PC send fax, optional in all other cases."
# StdLib
import sys
import struct
import os
import time
import getopt
import select
import signal
import tempfile
#import threading
#import Queue
from pickle import loads, HIGHEST_PROTOCOL
# Local
from base.g import *
from base.codes import *
from base import utils, device, status, models, module, services, os_utils
from base.sixext import PY3
from base.sixext import to_bytes_utf8
# dBus
try:
from dbus import lowlevel, SystemBus, SessionBus
import dbus.service
from dbus.mainloop.glib import DBusGMainLoop
if PY3:
try:
from gi._gobject import MainLoop, timeout_add, threads_init, io_add_watch, IO_IN #python3-gi version: 3.4.0
except:
from gi.repository.GLib import MainLoop, timeout_add, threads_init, io_add_watch, IO_IN #python3-gi version: 3.8.0
else:
from gobject import MainLoop, timeout_add, threads_init, io_add_watch, IO_IN
dbus_loaded = True
except ImportError:
log.error("dbus failed to load (python-dbus ver. 0.80+ required). Exiting...")
dbus_loaded = False
sys.exit(1)
import warnings
# Ignore: .../dbus/connection.py:242: DeprecationWarning: object.__init__() takes no parameters
# (occurring on Python 2.6/dBus 0.83/Ubuntu 9.04)
warnings.simplefilter("ignore", DeprecationWarning)
# Globals
PIPE_BUF = 4096
dbus_loop, main_loop = None, None
system_bus = None
session_bus = None
w1, w2, r3 = None, None, None
devices = {} # { 'device_uri' : DeviceCache, ... }
# ***********************************************************************************
#
# DEVICE CACHE
#
# ***********************************************************************************
class DeviceCache(object):
def __init__(self, model=''):
self.history = utils.RingBuffer(prop.history_size) # circular buffer of device.Event
self.model = models.normalizeModelName(model)
self.cache = {} # variable name : value
self.faxes = {} # (username, jobid): FaxEvent
self.dq = {} # last device query results
#self.backoff = False
self.backoff_counter = 0 # polling backoff: 0 = none, x = backed off by x intervals
self.backoff_countdown = 0
self.polling = False # indicates whether its in the device polling list
# dbus interface on session bus
class StatusService(dbus.service.Object):
def __init__(self, name, object_path):
dbus.service.Object.__init__(self, name, object_path)
@dbus.service.method('com.hplip.StatusService', in_signature='s', out_signature='sa(ssisisd)')
def GetHistory(self, device_uri):
log.debug("GetHistory('%s')" % device_uri)
send_systray_blip()
try:
devices[device_uri]
except KeyError:
#log.warn("Unknown device URI: %s" % device_uri)
return (device_uri, [])
else:
h = devices[device_uri].history.get()
log.debug("%d events in history:" % len(h))
[x.debug() for x in h]
return (device_uri, [x.as_tuple() for x in h])
@dbus.service.method('com.hplip.StatusService', in_signature='s', out_signature='sa{ss}')
def GetStatus(self, device_uri):
log.debug("GetStatus('%s')" % device_uri)
send_systray_blip()
try:
devices[device_uri]
except KeyError:
#log.warn("Unknown device URI: %s" % device_uri)
return (device_uri, {})
else:
t = {}
dq = devices[device_uri].dq
[t.setdefault(x, str(dq[x])) for x in list(dq.keys())]
log.debug(t)
return (device_uri, t)
@dbus.service.method('com.hplip.StatusService', in_signature='ssi', out_signature='i')
def SetCachedIntValue(self, device_uri, key, value):
log.debug("SetCachedIntValue('%s', '%s', %d)" % (device_uri, key, value))
if check_device(device_uri) == ERROR_SUCCESS:
devices[device_uri].cache[key] = value
return value
return -1
@dbus.service.method('com.hplip.StatusService', in_signature='ss', out_signature='i')
def GetCachedIntValue(self, device_uri, key):
try:
ret = devices[device_uri].cache[key]
except KeyError:
ret = -1
log.debug("GetCachedIntValue('%s', '%s') --> %d" % (device_uri, key, ret))
return ret
@dbus.service.method('com.hplip.StatusService', in_signature='sss', out_signature='s')
def SetCachedStrValue(self, device_uri, key, value):
log.debug("SetCachedStrValue('%s', '%s', '%s')" % (device_uri, key, value))
if check_device(device_uri) == ERROR_SUCCESS:
devices[device_uri].cache[key] = value
return value
return ''
@dbus.service.method('com.hplip.StatusService', in_signature='ss', out_signature='s')
def GetCachedStrValue(self, device_uri, key):
try:
ret = devices[device_uri].cache[key]
except KeyError:
ret = ''
log.debug("GetCachedStrValue('%s', '%s') --> %s" % (device_uri, key, ret))
return ret
# Pass a non-zero job_id to retrieve a specific fax
# Pass zero for job_id to retrieve any avail. fax
@dbus.service.method('com.hplip.StatusService', in_signature='ssi', out_signature='ssisisds')
def CheckForWaitingFax(self, device_uri, username, job_id=0):
log.debug("CheckForWaitingFax('%s', '%s', %d)" % (device_uri, username, job_id))
send_systray_blip()
r = (device_uri, '', 0, username, job_id, '', 0.0, '')
check_device(device_uri)
show_waiting_faxes(device_uri)
if job_id: # check for specific job_id
try:
devices[device_uri].faxes[(username, job_id)]
except KeyError:
return r
else:
return self.check_for_waiting_fax_return(device_uri, username, job_id)
else: # return any matching one from cache. call mult. times to get all.
for u, j in list(devices[device_uri].faxes.keys()):
if u == username:
return self.check_for_waiting_fax_return(device_uri, u, j)
return r
# if CheckForWaitingFax returns a fax job, that job is removed from the cache
def check_for_waiting_fax_return(self, d, u, j):
log.debug("Fax (username=%s, jobid=%d) removed from faxes and returned to caller." % (u, j))
r = devices[d].faxes[(u, j)].as_tuple()
del devices[d].faxes[(u, j)]
show_waiting_faxes(d)
return r
# Alternate way to "send" an event rather than using a signal message
@dbus.service.method('com.hplip.StatusService', in_signature='ssisis', out_signature='')
def SendEvent(self, device_uri, printer_name, event_code, username, job_id, title):
event = device.Event(device_uri, printer_name, event_code, username, job_id, title)
handle_event(event)
def check_device(device_uri):
if not PY3:
device_uri = str(device_uri)
try:
devices[device_uri]
except KeyError:
log.debug("New device: %s" % device_uri)
try:
back_end, is_hp, bus, model, serial, dev_file, host, zc, port = \
device.parseDeviceURI(device_uri)
except Error:
log.debug("Invalid device URI: %s" % device_uri)
return ERROR_INVALID_DEVICE_URI
devices[device_uri] = DeviceCache(model)
return ERROR_SUCCESS
def create_history(event):
history = devices[event.device_uri].history.get()
if history and history[-1].event_code == event.event_code:
log.debug("Duplicate event. Replacing previous event.")
devices[event.device_uri].history.replace(event)
return True
else:
devices[event.device_uri].history.append(event)
return False
def handle_fax_event(event, pipe_name):
if event.event_code == EVENT_FAX_RENDER_COMPLETE and \
event.username == prop.username:
fax_file_fd, fax_file_name = tempfile.mkstemp(prefix="hpfax-")
pipe = os.open(pipe_name, os.O_RDONLY)
bytes_read = 0
while True:
data = os.read(pipe, PIPE_BUF)
if not data:
break
os.write(fax_file_fd, data)
bytes_read += len(data)
log.debug("Saved %d bytes to file %s" % (bytes_read, fax_file_name))
os.close(pipe)
os.close(fax_file_fd)
devices[event.device_uri].faxes[(event.username, event.job_id)] = \
device.FaxEvent(fax_file_name, event)
show_waiting_faxes(event.device_uri)
try:
os.waitpid(-1, os.WNOHANG)
except OSError:
pass
# See if hp-sendfax is already running for this queue
ok, lock_file = utils.lock_app('hp-sendfax-%s' % event.printer_name, True)
if ok:
# able to lock, not running...
utils.unlock(lock_file)
path = utils.which('hp-sendfax')
if path:
path = os.path.join(path, 'hp-sendfax')
else:
log.error("Unable to find hp-sendfax on PATH.")
return
log.debug("Running hp-sendfax: %s --printer=%s" % (path, event.printer_name))
os.spawnlp(os.P_NOWAIT, path, 'hp-sendfax',
'--printer=%s' % event.printer_name)
else:
# cannot lock file - hp-sendfax is running
# no need to do anything... hp-sendfax is polling
log.debug("hp-sendfax is running. Waiting for CheckForWaitingFax() call.")
else:
log.warn("Not handled!")
pass
def show_waiting_faxes(d):
f = devices[d].faxes
if not len(f):
log.debug("No faxes waiting for %s" % d)
else:
if len(f) == 1:
log.debug("1 fax waiting for %s:" % d)
else:
log.debug("%d faxes waiting for %s:" % (len(f), d))
[f[x].debug() for x in f]
# Qt4 only
def handle_hpdio_event(event, bytes_written):
log.debug("Reading %d bytes from hpdio pipe..." % bytes_written)
total_read, data = 0, to_bytes_utf8('')
while True:
r, w, e = select.select([r3], [], [r3], 0.0)
if not r: break
x = os.read(r3, PIPE_BUF)
if not x: break
data = to_bytes_utf8('').join([data, x])
total_read += len(x)
if total_read == bytes_written: break
log.debug("Read %d bytes" % total_read)
if total_read == bytes_written:
dq = loads(data)
if check_device(event.device_uri) == ERROR_SUCCESS:
devices[event.device_uri].dq = dq.copy()
handle_event(device.Event(event.device_uri, '',
dq.get('status-code', STATUS_PRINTER_IDLE), prop.username, 0, ''))
send_toolbox_event(event, EVENT_DEVICE_UPDATE_REPLY)
def handle_plugin_install():
child_process=os.fork()
if child_process== 0: # child process
lockObj = utils.Sync_Lock("/tmp/pluginInstall.tmp")
lockObj.acquire()
child_pid=os.getpid()
from installer import pluginhandler
pluginObj = pluginhandler.PluginHandle()
if pluginObj.getStatus() != PLUGIN_INSTALLED:
os_utils.execute('hp-diagnose_plugin')
else:
log.debug("Device Plug-in was already installed. Not Invoking Plug-in installation wizard")
lockObj.release()
os.kill(child_pid,signal.SIGKILL)
else: #parent process
log.debug("Started Plug-in installation wizard")
def handle_printer_diagnose():
path = utils.which('hp-diagnose_queues')
if path:
path = os.path.join(path, 'hp-diagnose_queues')
else:
log.error("Unable to find hp-diagnose_queues on PATH.")
return
log.debug("Running hp-diagnose_queues: %s" % (path))
os.spawnlp(os.P_NOWAIT, path, 'hp-diagnose_queues','-s')
def handle_event(event, more_args=None):
#global polling_blocked
#global request_queue
# checking if any zombie child process exists. then cleaning same.
try:
os.waitpid(0, os.WNOHANG)
except OSError:
pass
log.debug("Handling event...")
if more_args is None:
more_args = []
event.debug()
if event.event_code == EVENT_AUTO_CONFIGURE:
handle_plugin_install()
return
if event.event_code == EVENT_DIAGNOSE_PRINTQUEUE:
handle_printer_diagnose()
return
if event.device_uri and check_device(event.device_uri) != ERROR_SUCCESS:
return
# If event-code > 10001, its a PJL error code, so convert it
if event.event_code > EVENT_MAX_EVENT:
event.event_code = status.MapPJLErrorCode(event.event_code)
# regular user/device status event
if event.event_code < EVENT_MIN_USER_EVENT:
pass
elif EVENT_MIN_USER_EVENT <= event.event_code <= EVENT_MAX_USER_EVENT:
if event.device_uri:
#event.device_uri = event.device_uri.replace('hpfax:', 'hp:')
dup_event = create_history(event)
if event.event_code in (EVENT_DEVICE_STOP_POLLING,
EVENT_START_MAINT_JOB,
EVENT_START_COPY_JOB,
EVENT_START_FAX_JOB,
EVENT_START_PRINT_JOB):
pass # stop polling (increment counter)
elif event.event_code in (EVENT_DEVICE_START_POLLING, # should this event force counter to 0?
EVENT_END_MAINT_JOB,
EVENT_END_COPY_JOB,
EVENT_END_FAX_JOB,
EVENT_END_PRINT_JOB,
EVENT_PRINT_FAILED_MISSING_PLUGIN,
EVENT_SCANNER_FAIL,
EVENT_END_SCAN_JOB,
EVENT_SCAN_FAILED_MISSING_PLUGIN,
EVENT_FAX_JOB_FAIL,
EVENT_FAX_JOB_CANCELED,
EVENT_FAX_FAILED_MISSING_PLUGIN,
EVENT_COPY_JOB_FAIL,
EVENT_COPY_JOB_CANCELED):
pass # start polling if counter <= 0
# TODO: Do tools send END event if canceled or failed? Should they?
# TODO: What to do if counter doesn't hit 0 after a period? Timeout?
# TODO: Also, need to deal with the backoff setting (or it completely sep?)
# Send to system tray icon if available
if not dup_event: # and event.event_code != STATUS_PRINTER_IDLE:
send_event_to_systray_ui(event)
# send EVENT_HISTORY_UPDATE signal to hp-toolbox
send_toolbox_event(event, EVENT_HISTORY_UPDATE)
if event.event_code in (EVENT_PRINT_FAILED_MISSING_PLUGIN, EVENT_SCAN_FAILED_MISSING_PLUGIN,EVENT_FAX_FAILED_MISSING_PLUGIN):
handle_plugin_install()
# Handle fax signals
elif EVENT_FAX_MIN <= event.event_code <= EVENT_FAX_MAX and more_args:
log.debug("Fax event")
pipe_name = str(more_args[0])
handle_fax_event(event, pipe_name)
elif event.event_code == EVENT_USER_CONFIGURATION_CHANGED:
# Sent if polling, hiding, etc. configuration has changed
# send_event_to_hpdio(event)
send_event_to_systray_ui(event)
elif event.event_code == EVENT_SYS_CONFIGURATION_CHANGED: # Not implemented
#send_event_to_hpdio(event)
send_event_to_systray_ui(event)
# Qt4 only
elif event.event_code in (EVENT_DEVICE_UPDATE_REQUESTED,):
#EVENT_DEVICE_START_POLLING, # ? Who handles polling? hpssd? probably...
#EVENT_DEVICE_STOP_POLLING): # ?
send_event_to_hpdio(event)
# Qt4 only
elif event.event_code in (EVENT_DEVICE_UPDATE_ACTIVE,
EVENT_DEVICE_UPDATE_INACTIVE):
send_event_to_systray_ui(event)
# Qt4 only
elif event.event_code == EVENT_DEVICE_UPDATE_REPLY:
bytes_written = int(more_args[1])
handle_hpdio_event(event, bytes_written)
# Qt4 only
elif event.event_code == EVENT_CUPS_QUEUES_ADDED or event.event_code == EVENT_CUPS_QUEUES_REMOVED:
send_event_to_systray_ui(event)
send_toolbox_event(event, EVENT_HISTORY_UPDATE)
# Qt4 only
elif event.event_code == EVENT_SYSTEMTRAY_EXIT:
send_event_to_hpdio(event)
send_toolbox_event(event)
send_event_to_systray_ui(event)
log.debug("Exiting")
main_loop.quit()
elif event.event_code in (EVENT_DEVICE_START_POLLING,
EVENT_DEVICE_STOP_POLLING):
pass
else:
log.error("Unhandled event: %d" % event.event_code)
def send_systray_blip():
send_event_to_systray_ui(device.Event('', '', EVENT_DEVICE_UPDATE_BLIP))
def send_event_to_systray_ui(event, event_code=None):
e = event.copy()
if event_code is not None:
e.event_code = event_code
e.send_via_pipe(w1, 'systemtray')
def send_event_to_hpdio(event):
event.send_via_pipe(w2, 'hpdio')
def send_toolbox_event(event, event_code=None):
global session_bus
e = event.copy()
if event_code is not None:
e.event_code = event_code
e.send_via_dbus(session_bus, 'com.hplip.Toolbox')
def handle_signal(typ, *args, **kwds):
if kwds['interface'] == 'com.hplip.StatusService' and \
kwds['member'] == 'Event':
event = device.Event(*args[:6])
return handle_event(event, args[6:])
def handle_system_signal(*args, **kwds):
return handle_signal('system', *args, **kwds)
def handle_session_signal(*args, **kwds):
return handle_signal('session', *args, **kwds)
def run(write_pipe1=None, # write pipe to systemtray
write_pipe2=None, # write pipe to hpdio
read_pipe3=None): # read pipe from hpdio
global dbus_loop, main_loop
global system_bus, session_bus
global w1, w2, r3
log.set_module("hp-systray(hpssd)")
log.debug("PID=%d" % os.getpid())
w1, w2, r3 = write_pipe1, write_pipe2, read_pipe3
dbus_loop = DBusGMainLoop(set_as_default=True)
main_loop = MainLoop()
try:
system_bus = SystemBus(mainloop=dbus_loop)
except dbus.exceptions.DBusException as e:
log.error("Unable to connect to dbus system bus. Exiting.")
sys.exit(1)
try:
session_bus = dbus.SessionBus()
except dbus.exceptions.DBusException as e:
if os.getuid() != 0:
log.error("Unable to connect to dbus session bus. Exiting.")
sys.exit(1)
else:
log.error("Unable to connect to dbus session bus (running as root?)")
sys.exit(1)
# Receive events from the system bus
system_bus.add_signal_receiver(handle_system_signal, sender_keyword='sender',
destination_keyword='dest', interface_keyword='interface',
member_keyword='member', path_keyword='path')
# Receive events from the session bus
session_bus.add_signal_receiver(handle_session_signal, sender_keyword='sender',
destination_keyword='dest', interface_keyword='interface',
member_keyword='member', path_keyword='path')
# Export an object on the session bus
session_name = dbus.service.BusName("com.hplip.StatusService", session_bus)
status_service = StatusService(session_name, "/com/hplip/StatusService")
log.debug("Entering main dbus loop...")
try:
main_loop.run()
except KeyboardInterrupt:
log.debug("Ctrl-C: Exiting...")