xpra icon
Bug tracker and wiki

Ticket #598: printing.patch

File printing.patch, 32.5 KB (added by Antoine Martin, 5 years ago)

work in progress patch

  • cups/xpraforwarder

     
     1#!/usr/bin/env python
     2# -*- coding: utf-8 -*-
     3#
     4# xpraforwarder - A CUPS backend written in Python. It uses GhostScript to create a
     5# PDF document and sends it via xpra to the client that requested the print.
     6# It is based on pdf2email by Georde Notaras.
     7#
     8# Copyright (c) George Notaras <George [D.O.T.] Notaras [A.T.] gmail [D.O.T.] com>
     9#
     10# License: GPLv2
     11#
     12# This program is released with absolutely no warranty, expressed or implied,
     13# and absolutely no support.
     14#
     15# This program is free software; you can redistribute it and/or modify
     16# it under the terms of the GNU General Public License as published by
     17# the Free Software Foundation; either version 2 of the License, or
     18# (at your option) any later version.
     19#
     20# This program is distributed in the hope that it will be useful,
     21# but WITHOUT ANY WARRANTY; without even the implied warranty of
     22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     23# GNU General Public License for more details.
     24#
     25# You should have received a copy of the GNU General Public License
     26# along with this program; if not, write to the:
     27#
     28# Free Software Foundation, Inc.,
     29# 59 Temple Place, Suite 330, Boston,
     30# MA 02111-1307  USA
     31#
     32
     33
     34import sys, os, syslog, datetime
     35
     36__version__ = "0.14"
     37
     38
     39def get_writable_dir():
     40        """Returns the writable directory path.
     41       
     42        The Device URI of the printer is: {backend_name}:{/path/to/writable/dir}
     43        This is set in /etc/cups/printers.conf and is kept in an environmental
     44        variable named "DEVICE_URI" while this process runs.
     45        """
     46        dev_uri = os.environ['DEVICE_URI']
     47        write_dir = dev_uri.split(":")[1].strip()
     48        if os.path.exists(write_dir):
     49                if os.access(write_dir, os.R_OK | os.W_OK):
     50                        return write_dir
     51                else:
     52                        raise Exception("User does not have read/write access to: %s" % write_dir)
     53        else:
     54                raise Exception("Device URI: Path does not exist: %s" % write_dir)
     55
     56
     57def get_output_filename(request_datetime):
     58        """Returns a formatted filename.
     59       
     60        Filename format: {user}_{job-id}_{date_time}.pdf"""
     61        return "%s_%s_%s.pdf" % (sys.argv[2], sys.argv[1], request_datetime.strftime("%Y%m%d_%H%M%S"))
     62
     63
     64def get_output_filepath(request_datetime):
     65        """Returns the full path to the output pdf file."""
     66        return os.path.join(get_writable_dir(), get_output_filename(request_datetime))
     67
     68
     69def create_pdf(output_file):
     70        """Creates the PDF file.
     71       
     72        Runs the GS interpreter as a child process and returns the exit code.
     73       
     74        External Tip: when creating pdf files with GS, an initial 'save' helps so
     75                                  that fonts are not flushed between pages. (tip source unknown)
     76        -sPAPERSIZE acts like a default value and needs to be set, but does not affect the final print.
     77        """
     78        command = "gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -sPAPERSIZE=a4 -dPDFSETTINGS=/printer -sOutputFile=%s -c save pop -f - &> /dev/null" % output_file
     79        fpipe = os.popen(command)
     80        ret_code = fpipe.close()        # This is the child process' exit code. None on success.
     81        return ret_code
     82
     83
     84def logger(msg):
     85        """Writes a syslog entry (msg) at the default facility and on level ERROR."""
     86        syslog.syslog(syslog.LOG_ERR, "CUPS PDF backend: %s" % msg)
     87
     88
     89def main():
     90        if len(sys.argv) == 1:
     91                # Without arguments should give backend info.
     92                # This is also used when lpinfo -v is issued, where it should include "direct this_backend"
     93                sys.stdout.write("direct %s \"Unknown\" \"Direct PDF Printing/Forwarding to host via xpra\"\n" % os.path.basename(sys.argv[0]))
     94                sys.stdout.flush()
     95                sys.exit(0)
     96        if len(sys.argv) not in (5,6):
     97                sys.stdout.write("Usage: %s job-id user title copies options [file]\n" % os.path.basename(sys.argv[0]))
     98                sys.stdout.flush()
     99                logger("Wrong number of arguments. Usage: %s job-id user" % sys.argv[0])
     100                sys.exit(1)
     101        request_datetime = datetime.datetime.now()
     102        try:
     103                output_file = get_output_filepath(request_datetime)
     104        except Exception, err:
     105                if err[0] <> "DEVICE_URI":      # (TODO) RE-EXAMINE THIS - This occurs when CUPS is (re)started. Probably an internal backend check.
     106                        logger(err[0])
     107        else:
     108                ret_code = create_pdf(output_file)
     109                pdferror = ret_code!=0
     110                if pdferror:
     111                        logger("Failed to create PDF document")
     112                try:
     113                        pass
     114                        #send_email(output_file, request_datetime, pdferror)
     115                except:
     116                        logger("Failed to forward the PDF document to user: %s" % sys.argv[2])
     117                if os.path.exists(output_file):
     118                        os.remove(output_file)
     119
     120
     121if __name__=='__main__':
     122        main()
     123
  • etc/xpra/xpra.conf.in

    Property changes on: cups/xpraforwarder
    ___________________________________________________________________
    Added: svn:executable
    ## -0,0 +1 ##
    +*
    \ No newline at end of property
     
    4141
    4242
    4343################################################################################
     44# File transfer
     45
     46# Receive files
     47file-transfer = yes
     48
     49# Location where files are received:
     50#download-path = ~/Downloads
     51#download-path = ~/Deskto
     52#download-path = /tmp
     53
     54# File size limit in MB
     55file-size-limit = 10
     56
     57# Print support:
     58printing = yes
     59
     60# How to open filee:
     61#open-command = xdg-open
     62
     63# Open files
     64open-files = no
     65
     66
     67################################################################################
    4468# Picture Encoding
    4569
    4670# Default encoding
  • setup.py

     
    122122if WIN32 or OSX:
    123123    Xdummy_ENABLED = False
    124124sound_ENABLED           = True
     125printing_ENABLED        = not WIN32
    125126
    126127enc_proxy_ENABLED       = True
    127128enc_x264_ENABLED        = True          #too important to detect
     
    176177            "clipboard",
    177178            "server", "client", "x11", "gtk_x11",
    178179            "gtk2", "gtk3", "qt4", "html5",
    179             "sound", "cyxor", "cymaths", "opengl", "argb",
     180            "sound", "printing",
     181            "cyxor", "cymaths", "opengl", "argb",
    180182            "warn", "strict", "shadow", "debug", "PIC", "Xdummy", "verbose", "bundle_tests")
    181183HELP = "-h" in sys.argv or "--help" in sys.argv
    182184if HELP:
     
    14721474        add_data_files(html5_dir+k, v)
    14731475
    14741476
     1477if printing_ENABLED:
     1478    add_data_files("/usr/lib/cups/backend", ["cups/xpraforwarder"])
    14751479
     1480
     1481
    14761482#*******************************************************************************
    14771483#which file to link against (new-style buffers or old?):
    14781484if memoryview_ENABLED:
  • xpra/client/client_base.py

     
    2121from xpra.platform.features import GOT_PASSWORD_PROMPT_SUGGESTION
    2222from xpra.platform.info import get_name
    2323from xpra.os_util import get_hex_uuid, get_machine_id, get_user_uuid, load_binary_file, SIGNAMES, strtobytes, bytestostr
    24 from xpra.util import typedict, xor
     24from xpra.util import typedict, xor, nonl
    2525
    2626EXIT_OK = 0
    2727EXIT_CONNECTION_LOST = 1
     
    8282        self.min_quality = 0
    8383        self.speed = 0
    8484        self.min_speed = -1
     85        self.file_transfer = False
     86        self.printing = False
     87        self.open_command = None
    8588        #protocol stuff:
    8689        self._protocol = None
    8790        self._priority_packets = []
     
    113116        self.min_quality = opts.min_quality
    114117        self.speed = opts.speed
    115118        self.min_speed = opts.min_speed
     119        self.file_transfer = opts.file_transfer
     120        self.printing = opts.printing
     121        self.open_command = opts.open_command
    116122
    117123        if DETECT_LEAKS:
    118124            from xpra.util import detect_leaks
     
    171177
    172178    def init_packet_handlers(self):
    173179        self._packet_handlers = {
    174             "hello": self._process_hello,
     180            "hello"             : self._process_hello,
     181            "query-printers"    : self._process_query_printers,
     182            "send-file"         : self._process_send_file,
    175183            }
    176184        self._ui_packet_handlers = {
    177185            "challenge": self._process_challenge,
     
    219227                "version"               : local_version,
    220228                "encoding.generic"      : True,
    221229                "namespace"             : True,
     230                "file-transfer"         : self.file_transfer,
     231                "printing"              : self.printing,
    222232                "hostname"              : socket.gethostname(),
    223233                "uuid"                  : self.uuid,
    224234                "username"              : self.username,
     
    256266
    257267    def make_hello(self):
    258268        capabilities = {
    259                         "randr_notify"        : False,        #only client.py cares about this
    260                         "windows"            : False,        #only client.py cares about this
     269                        "randr_notify"        : False,      #only client.py cares about this
     270                        "windows"            : False,       #only client.py cares about this
    261271                       }
    262272        if self._reverse_aliases:
    263273            capabilities["aliases"] = self._reverse_aliases
     
    485495        #legacy, should not be used for anything
    486496        pass
    487497
     498
     499    def _process_query_printers(self, packet):
     500        from xpra.platform.printing import getPrinters
     501        printers = getPrinters()
     502        self.send("printers", printers)
     503
     504
     505    def _process_send_file(self, packet):
     506        #send-file basefilename, printit, openit, filesize, 0, data)
     507        assert self.file_transfer
     508        from xpra.platform.features import DOWNLOAD_PATH
     509        basefilename, printit, openit, filesize, data, maxbitrate, printer, title, options = packet[1:10]
     510        assert maxbitrate>=0
     511        assert filesize>0 and data
     512        filename = os.path.abspath(os.path.join(os.path.expanduser(DOWNLOAD_PATH), os.path.basename(basefilename)))
     513        if os.path.exists(filename):
     514            log.error("cannot save file %s: file already exists", filename)
     515            return
     516        #TODO: maybe use mkstemp instead of EXCL?
     517        fd = os.open(filename, os.O_CREAT | os.O_RDWR | os.O_EXCL)
     518        f = os.fdopen(fd,'wb')
     519        try:
     520            f.write(data)
     521        finally:
     522            f.close()
     523        log.info("downloaded %s bytes to %s", filesize, filename)
     524        if printit:
     525            assert self.printing
     526            self._print_file(filename, printer, title, options)
     527            return
     528        elif openit:
     529            #run the command in a new thread
     530            #so we can block waiting for the subprocess to exit
     531            #(ensures that we do reap the process)
     532            import thread
     533            thread.start_new_thread(self._open_file, (filename, ))
     534
     535    def _print_file(self, filename, printer, title, options):
     536        from xpra.platform.printing import printFiles
     537        job = printFiles(printer, [filename], title, options)
     538        log("printing %s, job=%s", filename, job)
     539
     540    def _open_file(self, filename):
     541        import subprocess
     542        PIPE = subprocess.PIPE
     543        process = subprocess.Popen([self.open_command, filename], stdin=PIPE, stdout=PIPE, stderr=PIPE)
     544        out, err = process.communicate()
     545        r = process.wait()
     546        log.info("opened file %s with %s, exit code: %s", filename, self.open_command, r)
     547        if r!=0:
     548            l = log.warn
     549        else:
     550            l = log
     551        if out:
     552            l("stdout=%s", nonl(out)[:512])
     553        if err:
     554            l("stderr=%s", nonl(err)[:512])
     555
     556
    488557    def _process_gibberish(self, packet):
    489558        (_, data) = packet
    490559        log.info("Received uninterpretable nonsense: %s", repr(data))
  • xpra/client/ui_client_base.py

     
    19141914    def init_packet_handlers(self):
    19151915        XpraClientBase.init_packet_handlers(self)
    19161916        for k,v in {
    1917             "hello":                self._process_hello,
    19181917            "startup-complete":     self._startup_complete,
    19191918            "new-window":           self._process_new_window,
    19201919            "new-override-redirect":self._process_new_override_redirect,
  • xpra/platform/darwin/printing.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2014 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6# darwin: assume cups support (no overrides needed here)
  • xpra/platform/features.py

     
    44# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    55# later version. See the file COPYING for details.
    66
     7import os.path
     8
    79#defaults which may be overriden by platform_import:
    810LOCAL_SERVERS_SUPPORTED = False
    911SHADOW_SUPPORTED = False
     
    1719CLIPBOARD_NATIVE_CLASS = None
    1820CAN_DAEMONIZE = False
    1921UI_THREAD_POLLING = 0
     22OPEN_COMMAND = "xdg-open"
     23DOWNLOAD_PATH = "~/Downloads"
     24if not os.path.exists(os.path.expanduser(DOWNLOAD_PATH)):
     25    DOWNLOAD_PATH = "~"
    2026
    2127from xpra.platform import platform_import
    2228platform_import(globals(), "features", False,
     
    2733                "SYSTEM_TRAY_SUPPORTED",
    2834                "DEFAULT_SSH_CMD",
    2935                "GOT_PASSWORD_PROMPT_SUGGESTION",
     36                "DOWNLOAD_PATH",
    3037                "CLIPBOARDS",
    3138                "CLIPBOARD_WANT_TARGETS",
    3239                "CLIPBOARD_GREEDY",
  • xpra/platform/printing.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2014 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6#default implementation uses pycups
     7from xpra.log import Logger
     8log = Logger("printing")
     9
     10def err(*args):
     11    log.error(*args)
     12
     13def getPrinters():
     14    return {}
     15
     16def printFiles(printer, filenames, title, options):
     17    raise Exception("no print implementation available")
     18
     19def printingFinished(printpid):
     20    return True
     21   
     22
     23#default implementation uses pycups:
     24from xpra.platform import platform_import
     25try:
     26    from xpra.platform.pycups_printing import getPrinters, printFiles, printingFinished
     27    assert getPrinters and printFiles and printingFinished
     28except Exception, e:
     29    err("cannot use pycups for printing: %s", e)
     30
     31platform_import(globals(), "printing", False,
     32                "getPrinters",
     33                "printFiles",
     34                "printingFinished")
  • xpra/platform/pycups_printing.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2014 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6#default implementation using pycups
     7import cups
     8
     9
     10def err(*args):
     11    from xpra.log import Logger
     12    log = Logger("printing")
     13    log.error(*args)
     14
     15def getPrinters():
     16    conn = cups.Connection()
     17    printers = conn.getPrinters()
     18    return printers
     19
     20def printFiles(printer, filenames, title, options):
     21    conn = cups.Connection()
     22    printpid = conn.printFiles(printer, filenames, title, options)
     23    return printpid
     24
     25def printingFinished(printpid):
     26    conn = cups.Connection()
     27    return conn.getJobs().get(printpid, None) is None
  • xpra/platform/win32/features.py

     
    77# Platform-specific code for Win32.
    88import os
    99
     10def get_registry_value(key, reg_path, entry):
     11    import win32api             #@UnresolvedImport
     12    hKey = win32api.RegOpenKey(key, reg_path)
     13    value, _ = win32api.RegQueryValueEx(hKey, entry)
     14    win32api.RegCloseKey(hKey)
     15    return    value
     16
     17
    1018SYSTEM_TRAY_SUPPORTED = True
    1119SHADOW_SUPPORTED = True
    1220os.environ["PLINK_PROTOCOL"] = "ssh"
    1321DEFAULT_SSH_CMD = "plink"
     22PRINT_COMMAND = ""
     23#TODO: use "FOLDERID_Downloads":
     24# FOLDERID_Downloads = "{374DE290-123F-4565-9164-39C4925E467B}"
     25# maybe like here:
     26# https://gist.github.com/mkropat/7550097
     27#from win32com.shell import shell, shellcon
     28#shell.SHGetFolderPath(0, shellcon.CSIDL_MYDOCUMENTS, None, 0)
     29try:
     30    #use the internet explorer registry key:
     31    #HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer
     32    import win32con             #@UnresolvedImport
     33    DOWNLOAD_PATH = get_registry_value(win32con.HKEY_CURRENT_USER, "Software\\Microsoft\\Internet Explorer", "Download Directory")
     34except:
     35    #fallback to what the documentation says is the default:
     36    DOWNLOAD_PATH = os.path.join(os.environ.get("USERPROFILE", "~"), "Downloads")
    1437GOT_PASSWORD_PROMPT_SUGGESTION = \
    1538   'Perhaps you need to set up Pageant, or (less secure) use --ssh="plink -pw YOUR-PASSWORD"?\n'
    1639CLIPBOARDS=["CLIPBOARD"]
  • xpra/platform/win32/printing.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2014 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6from xpra.log import Logger
     7log = Logger("printing")
     8
     9import win32print       #@UnresolvedImport
     10import win32api         #@UnresolvedImport
     11
     12
     13def getPrinters():
     14    printers = {}
     15    #default_printer = win32print.GetDefaultPrinter()
     16    for p in win32print.EnumPrinters(win32print.PRINTER_ENUM_LOCAL, None, 1):
     17        flags, desc, name, comment = p
     18        log("found printer: %s, %s, %s, %s", flags, desc, name, comment)
     19        #phandle = win32print.OpenPrinter(name)
     20        #win32print.ClosePrinter(phandle)
     21        printers[name] = (desc, comment)
     22    log("printers=%s", printers)
     23    return printers
     24
     25def printFiles(printer, filenames, title, options):
     26    #AcroRd32.exe /N /T PdfFile PrinterName [ PrinterDriver [ PrinterPort ] ]
     27    for f in filenames:
     28        #win32api.ShellExecute(0, "print", f, '/d:"%s"' % printer, ".", 0)
     29        win32api.ShellExecute(0, "printto", f, '"%s"' % printer, ".", 0)
  • xpra/platform/xposix/printing.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2014 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6# posix: assume cups support (no overrides needed here)
  • xpra/scripts/config.py

     
    234234                    "window-layout"     : str,
    235235                    "display"           : str,
    236236                    "tcp-proxy"         : str,
     237                    "download-path"             : str,
     238                    "open-command"      : str,
    237239                    "debug"             : str,
    238240                    #int options:
    239241                    "quality"           : int,
     
    243245                    "port"              : int,
    244246                    "compression_level" : int,
    245247                    "dpi"               : int,
     248                    "file-size-limit"   : int,
    246249                    #float options:
    247250                    "max-bandwidth"     : float,
    248251                    "auto-refresh-delay": float,
     
    270273                    "sharing"           : bool,
    271274                    "delay-tray"        : bool,
    272275                    "windows"           : bool,
     276                    "file-transfer"             : bool,
     277                    "printing"                  : bool,
     278                    "open-files"                : bool,
    273279                    "autoconnect"       : bool,
    274280                    "exit-with-children": bool,
    275281                    "exit-with-client"  : bool,
     
    294300    global GLOBAL_DEFAULTS
    295301    if GLOBAL_DEFAULTS is not None:
    296302        return GLOBAL_DEFAULTS
    297     from xpra.platform.features import DEFAULT_SSH_CMD
     303    from xpra.platform.features import DEFAULT_SSH_CMD, DOWNLOAD_PATH, OPEN_COMMAND
    298304    try:
    299305        from xpra.platform.info import get_username
    300306        username = get_username()
     
    330336                    "window-layout"     : "",
    331337                    "display"           : "",
    332338                    "tcp-proxy"         : "",
     339                    "download-path"             : DOWNLOAD_PATH,
     340                    "open-command"      : OPEN_COMMAND,
    333341                    "debug"             : "",
    334342                    "quality"           : -1,
    335343                    "min-quality"       : 50,
     
    338346                    "port"              : -1,
    339347                    "compression_level" : 1,
    340348                    "dpi"               : 96,
     349                    "file-size-limit"   : 10,
    341350                    "max-bandwidth"     : 0.0,
    342351                    "auto-refresh-delay": 0.25,
    343352                    "daemon"            : True,
     
    363372                    "sharing"           : False,
    364373                    "delay-tray"        : False,
    365374                    "windows"           : True,
     375                    "file-transfer"             : True,
     376                    "printing"                  : True,
     377                    "open-files"                : False,
    366378                    "autoconnect"       : False,
    367379                    "exit-with-children": False,
    368380                    "exit-with-client"  : False,
  • xpra/scripts/main.py

     
    488488
    489489    options, args = parser.parse_args(cmdline[1:])
    490490
     491    #FIXME: file tranfer command line options:
     492    hidden_options["file_transfer"] = defaults.file_transfer
     493    hidden_options["file_size_limit"] = defaults.file_size_limit
     494    hidden_options["printing"] = defaults.printing
     495    hidden_options["open_command"] = defaults.open_command
     496    hidden_options["open_files"] = defaults.open_files
     497
    491498    #ensure all the option fields are set even though
    492499    #some options are not shown to the user:
    493500    for k,v in hidden_options.items():
  • xpra/server/server_base.py

     
    7272        self.supports_clipboard = False
    7373        self.supports_dbus_proxy = False
    7474        self.dbus_helper = None
     75        self.file_transfer = False
     76        self.printing = False
    7577
    7678        #encodings:
    7779        self.core_encodings = []
     
    9092                    "scaling",
    9193                    "suspend", "resume", "name", "ungrab",
    9294                    "key", "focus",
    93                     "client"]
     95                    "client",
     96                    "send-file", "print"]
    9497
    9598        self.init_encodings()
    9699        self.init_packet_handlers()
     
    136139        self.supports_clipboard = opts.clipboard
    137140        self.supports_dbus_proxy = opts.dbus_proxy
    138141        self.send_pings = opts.pings
     142        self.file_transfer = opts.file_transfer
     143        self.printing = opts.printing
    139144
    140145        log("starting component init")
    141146        self.init_clipboard(self.supports_clipboard, opts.clipboard_filter_file)
     
    330335            "set-notify":                           self._process_set_notify,
    331336            "set-bell":                             self._process_set_bell,
    332337            "command_request":                      self._process_command_request,
     338            "printers":                             self._process_printers,
    333339                                          }
    334340        self._authenticated_ui_packet_handlers = self._default_packet_handlers.copy()
    335341        self._authenticated_ui_packet_handlers.update({
     
    633639                 "bell"                         : self.bell,
    634640                 "cursors"                      : self.cursors,
    635641                 "dbus_proxy"                   : self.supports_dbus_proxy,
     642                 "file-transfer"                : self.file_transfer,
     643                 "printing"                     : self.printing,
    636644                 })
    637645            capabilities.update({
    638646             "toggle_cursors_bell_notify"   : True,
     
    700708        server_source.hello(capabilities)
    701709
    702710
     711    def _process_printers(self, proto, packet):
     712        ss = self._server_sources.get(proto)
     713        if ss is None:
     714            return
     715        printers = packet[1]
     716        ss.set_printers(printers)
     717
     718
    703719    def _process_command_request(self, proto, packet):
    704720        """ client sent a command request through its normal channel """
    705721        assert len(packet)>=2, "invalid command request packet (too small!)"
     
    757773                    raise Exception("invalid window id: %s" % x)
    758774            return wids
    759775
     776        def get_sources(client_uuids, attr=None):
     777            #find the client uuid specified:
     778            client_uuids = args[2].split(",")
     779            sources = [ss for ss in self._server_sources.values() if ss.uuid in client_uuids]
     780            notfound = [x for x in client_uuids if x not in [ss.uuid for ss in sources]]
     781            if notfound:
     782                commandlog.warn("client connection not found for uuid(s): %s", notfound)
     783            if attr:
     784                attrerr = [x for x in client_uuids if (x not in notfound and  getattr(ss, attr))]
     785                if attrerr:
     786                    commandlog.warn("client connections do not support %s: %s", attr, attrerr)
     787            return sources
     788
    760789        #handle commands that either don't require a client,
    761790        #or can work on more than one connected client:
    762791        if command in ("help", "hello"):
     
    960989                    commandlog.warn("client %s does not support client command %s", source, client_command[0])
    961990            csource.send_client_command(*client_command)
    962991            return 0, "client control command '%s' forwarded to %s clients" % (client_command[0], count)
     992        elif command=="send-file":
     993            #ie: send-file filename open|noopen client_uuids maxbitrate
     994            if len(args)!=4:
     995                return argn_err(4)
     996            filename = os.path.abspath(os.path.expanduser(args[0]))
     997            if not os.path.exists(filename):
     998                return arg_err("file '%s' does not exist" % filename)
     999            openit = args[1] in ("open", "true", "1")
     1000            #find the client uuid specified:
     1001            client_uuids = args[2].split(",")
     1002            sources = get_sources(client_uuids, "file_transfer")
     1003            if not sources:
     1004                return arg_err("no clients found matching: %s" % client_uuids)
     1005            maxbitrate = args[3]
     1006            for ss in sources:
     1007                assert ss.file_transfer
     1008                ss.send_file(filename, False, openit, ss, maxbitrate)
     1009            return 0, "file transfer to %s initiated" % client_uuids
     1010        elif command=="print":
     1011            #ie: print filename printer client_uuids maxbitrate title *options
     1012            if len(args)<4:
     1013                return argn_err("at least 4")
     1014            filename = os.path.abspath(os.path.expanduser(args[0]))
     1015            if not os.path.exists(filename):
     1016                return arg_err("file '%s' does not exist" % filename)
     1017            printer = args[1]
     1018            #find the client uuid specified:
     1019            client_uuids = args[2].split(",")
     1020            sources = get_sources(client_uuids, "printing")
     1021            if not sources:
     1022                return arg_err("no clients found matching: %s" % client_uuids)
     1023            maxbitrate = args[3]
     1024            title = ""
     1025            options = {}
     1026            if len(args)>=5:
     1027                title = args[4]
     1028            if len(args)>=6:
     1029                for arg in args[5:]:
     1030                    argp = arg.split("=", 1)
     1031                    if len(argp)==2 and len(argp[0])>0:
     1032                        options[argp[0]] = argp[1]
     1033            for ss in sources:
     1034                ss = sources[0]
     1035                assert ss.printing
     1036                ss.send_file(filename, True, True, ss, maxbitrate, printer, title, options)
     1037            return 0, "printing to %s initiated" % client_uuids
    9631038        else:
    9641039            return ServerCore.do_handle_command_request(self, command, args)
    9651040
  • xpra/server/source.py

     
    3030from xpra.codecs.codec_constants import codec_spec
    3131from xpra.net.protocol import compressed_wrapper, Compressed
    3232from xpra.daemon_thread import make_daemon_thread
    33 from xpra.os_util import platform_name, StringIOClass, thread, Queue, get_machine_id, get_user_uuid
     33from xpra.os_util import platform_name, StringIOClass, thread, Queue, get_machine_id, get_user_uuid, load_binary_file
    3434from xpra.server.background_worker import add_work_item
    3535from xpra.util import std, typedict
    3636
     
    320320        self.notify_startup_complete = False
    321321        self.control_commands = []
    322322        self.supports_transparency = False
     323        self.file_transfer = False
     324        self.printing = False
     325        self.printers = {}
    323326        #what we send back in hello packet:
    324327        self.ui_client = True
    325328        self.wants_aliases = True
     
    542545        self.namespace = c.boolget("namespace")
    543546        self.control_commands = c.strlistget("control_commands")
    544547        self.supports_transparency = HAS_ALPHA and c.boolget("encoding.transparency")
     548        self.file_transfer = c.boolget("file-transfer")
     549        self.printing = c.boolget("printing")
    545550
    546551        self.desktop_size = c.intpair("desktop_size")
    547552        if self.desktop_size is not None:
     
    10981103                i += 1
    10991104        for prop in ("named_cursors", "server_window_resize", "share", "randr_notify",
    11001105                     "clipboard_notifications", "raw_window_icons", "system_tray", "generic_window_types",
    1101                      "notify_startup_complete", "namespace", "lz4"):
     1106                     "notify_startup_complete", "namespace", "lz4", "printing"):
    11021107            addattr("features."+prop, prop)
    11031108        for prop, name in {"clipboard_enabled"  : "clipboard",
    11041109                           "send_windows"       : "windows",
     
    11291134            cv("speaker.%s" % k, v)
    11301135        for k,v in get_sound_info(self.supports_microphone, self.sound_sink).items():
    11311136            cv("microphone.%s" % k, v)
     1137        #printing:
     1138        if self.printers:
     1139            cv("printers", self.printers.keys())
    11321140
    11331141    def send_info_response(self, info):
    11341142        self.rewrite_encoding_values(info)
     
    12301238        self.send("set_deflate", level)
    12311239
    12321240
     1241    def set_printers(self, printers):
     1242        log("set_printers(%s)", printers)
     1243        self.printers = printers
     1244
     1245    def send_file(self, filename, printit, openit, ss, maxbitrate=0, printer="", title="", options={}):
     1246        log.info("send_file%s", (filename, printit, openit, ss, maxbitrate, printer, title, options))
     1247        #TODO:
     1248        # * client ACK
     1249        # * stream it (don't load the whole file!)
     1250        # * timeouts
     1251        # * checksum
     1252        # * rate control
     1253        # * xpra info with queue and progress
     1254        basefilename = os.path.basename(filename)
     1255        filesize = os.path.getsize(filename)
     1256        data = load_binary_file(filename)
     1257        assert filesize>0 and data
     1258        cdata = compressed_wrapper("file-data", data, level=5, lz4=self.lz4)
     1259        self.send("send-file", basefilename, printit, openit, filesize, cdata, maxbitrate, printer, title, options)
     1260
    12331261    def send_client_command(self, *args):
    12341262        self.send("control", *args)
    12351263