通过Python调用Spice-gtk

序言

通过Virt Manager研究学习Spice gtk的Python方法

你将学到什么

Virt Manager研究

显示代码定位

首先我们使用Virt Manager来观察桌面连接窗口
661136-20171014113357168-1513670421.png

然后我们使用glade打开Virt Manager源码目录下的ui目录下的文件进行比对,发现details.ui就是我们前面见到的窗口,对应的处理代码为details.py,并且我们知道对应的处理信号为on_control_vm_console_toggled

661136-20171014113427809-1197322767.png

接着我们在details.py文件找到对应处理函数

# 绑定信号处理函数
"on_control_vm_console_toggled": self.details_console_changed,
# 处理函数核心逻辑
pages.set_current_page(DETAILS_PAGE_CONSOLE)

分析函数我们知道信号的处理逻辑就是切换到DETAILS_PAGE_CONSOLE页面,然后我们找到对应的页面知道对应的处理信号为on_console_pages_switch_page

661136-20171014113446293-1058275015.png

查找信号对应的处理函数,我们知道最终的显示代码位于console.py中,继续分析UI文件,我们知道我们最终要关注的部件就是console-gfx-viewport,接下来我们来研究下console.py文件的vmmConsolePages

661136-20171014113458934-32221287.png

vmmConsolePages类分析

首先分析类的初始化代码,我们发现如下注释

def __init__(self, vm, builder, topwin)
    # Initialize display widget
    self._viewer = None

很显然这个**_viewer就是用作显示的widget,查找代码找到_viewer**赋值处

def _init_viewer(self):
# 省略部分代码
try:
    if ginfo.gtype == "vnc":
        viewer_class = VNCViewer
    elif ginfo.gtype == "spice":
        if have_spice_gtk:
            viewer_class = SpiceViewer
        else:
            raise RuntimeError("Error opening Spice console, "
 "SpiceClientGtk missing")

    self._viewer = viewer_class(self.vm, ginfo)
    self._connect_viewer_signals()

    self._refresh_enable_accel()

    self._viewer.console_open()
except Exception, e:
    logging.exception("Error connection to graphical console")
    self._activate_unavailable_page(
            _("Error connecting to graphical console") + ":\n%s" % e)

很显然SpiceViewer就是实现spice gtk调用的类,它位于viewers.py文件中,在研究这个类之前我们要先研究下传入的两个参数viewer_class(self.vm, ginfo),其中self.vm是一个vmmDomain类对象(位于domain.py文件),ginfo变量是一个ConnectionInfo类对象(位于sshtunnels.py文件)。ConnectionInfo类对象作用就是保存套接字服务端信息,vmmDomain类对象作用就是保存libvirt虚拟机信息。

SpiceViewer代码分析

首先我们来看下官方文档的描述

SpiceSession处理所有SpiceChannel连接,它同时还保存了连接信息比如服务器地址和端口。你也可以简单的设置"uri"比如 spice://127.0.0.1?port=5930 来设置你的连接信息。通过关联channel-new信号到SpiceSession对象,你将会收到连接就绪通知,并进行互动。比如SpiceInputsChannel准备就绪并且收到SPICE_CHANNEL_OPENED事件,你就可以通过spice_inputs_key_press()来发送按键事件;如果SpiceMainChannel处于可用状态,你就可以开始共享剪切板了等等。一旦SpiceSession对象属性设置完成,我们就能通过调用spice_session_connect()开始与Spice server的通信。

通过官网的描述使用SpiceSession的步骤如下:

  • 创建SpiceSession对象
  • 关联channel-new信号
  • 设置连接信息
  • 调用spice_session_connect

SpiceViewer的调用顺序为**console_open -> _open -> _open_host/_open_fd(完成了后两步) -> _create_spice_session(完成了前两步),接下来我们主要分析channel-new信号的处理函数_channel_new_cb里面描述了对Display Channel**的处理。

最简单的显示代码
# -*- coding: utf-8 -*-
import gi
gi.require_version('SpiceClientGtk', '3.0')
gi.require_version('Gtk', '3.0')
from gi.repository import SpiceClientGtk,SpiceClientGLib,Gtk,GObject

class Spicy(Gtk.Window):
    def __init__(self, host, port):
        Gtk.Window.__init__(self, title="Spicy")
        self._display = None
        self._spice_session = SpiceClientGLib.Session()
        GObject.GObject.connect(self._spice_session, "channel-new", self._channel_new_cb)
        self._spice_session.set_property("host", str(host))
        self._spice_session.set_property("port", str(port))
        self._spice_session.connect()

    def _channel_new_cb(self, session, channel):
        if (type(channel) == SpiceClientGLib.DisplayChannel and not self._display):
            channel_id = channel.get_property("channel-id")
            self._display_channel = channel
            self._display = SpiceClientGtk.Display.new(self._spice_session, channel_id)
            self._display.show()
            self.add(self._display)

    def _fd_channel_event_cb(self, channel, event):
        channel.disconnect_by_func(self._fd_channel_event_cb)

    def _channel_open_fd_request(self, channel, tls_ignore):
        channel.connect_after("channel-event", self._fd_channel_event_cb)

        fd = self._get_fd_for_open()
        channel.open_fd(fd)

window = Spicy("127.0.0.1", "5900")
window.connect("delete-event", Gtk.main_quit)
window.show_all()
Gtk.main()

编写Spicy

类名说明用户需要处理的信号用户需要读取的属性
Spice Session负责客户端和服务器的套接字连接channel-new: 根据服务器的配置在套接字上建立对应的通道 channel-destroy: 关闭通道name: SPICE服务名 password: TLS密码 host: SPICE主机地址 port: 未加密会话端口 tls-port: 加密会话端口
Spice Channel负责功能模块的通信channel-event: 监听通道事件 open-fd:通道与套接字的绑定
Spice Display负责显示部件的建立mouse-grab: 鼠标捕获与释放

Viewer: 远程桌面连接客户端的Gtk.Widget,对Spice Display的封装,需要提供以下功能

  • 根据传入的服务器信息,能建立和关闭与服务器的会话连接
  • 在Spice Display显示部件完成初始化后能发出通知,使得父窗口能自动完成显示部件添加动作
基类Viewer

公共方法

方法名参数说明
def open(self)None建立与服务器的会话连接,分为两种方式,一种是使用用户传入的套接字建立会话,一种是协议自身根据传入的服务器信息建立套接字,然后建立会话
def close(self)None关闭与服务器的会话连接,需要子类根据自身资源重写
def remove_display_widget(self, widget)widget: 包含显示部件的父窗口关闭与服务器的会话连接

私有方法

方法名参数说明
def _get_fd_for_open(self)None获取用户传入的套接字,如果用户只传入服务器信息则返回None
def _open_fd(self, fd)fd: 用户传入的套接字句柄使用用户传入的套接字建立会话
def _open_host(self)None协议自身根据传入的服务器信息建立套接字并建立会话
SpiceViewer

重写方法

方法名参数说明
def close(self)None释放显示部件,并断开Spice会话连接
def _open_fd(self, fd)fd: 用户传入的套接字句柄使用用户传入的套接字建立Spice会话
def _open_host(self)None根据传入的服务器信息设置Spice会话属性,Spice会话将根据设置的服务器属性自动建立会话连接
def _create_session(self)None建立Spice会话对象并监听channel创建消息
def _channel_new_cb(self, session, channel)session: Spice会话 channel: Spice通道针对不同通道的建立做不同处理,例如当检测到显示通道建立时,创建显示部件,并触发显示部件添加消息
def _channel_open_fd_cb(self, channel, with_tls)channel: 没有fd关联的Spice通道 with_tls: 是不是SSH连接Spice通道在连接时如果没有检测到Spice会话关联的fd,则触发此回调,根据Spice会话保存的服务器信息创建会话连接,然后将连接的套接字句柄关联到Spice通道上,然后再次执行通道连接动作
def _channel_event_cb(self, channel, event)channel: Spice通道 event: 事件如果通道关闭时出现异常就会触发此回调
def _mouse_grab_cb(self, display, status)display: 显示部件 status
: 鼠标捕获状态显示部件捕获丢失鼠标时触发此回调,status为1表示捕获鼠标,0表示丢失鼠标,我们需要把这消息继续路由出去,让使用者能做更多控制
def _grab_keys_pressed_cb(self, display)display: 显示部件当释放鼠标键盘的组合键被按下时触发此回调,我们还要继续把这消息路由出去,让使用者能做更多控制
样例代码

viewers.py文件

# -*- coding: utf-8 -*-

import gi
gi.require_version('SpiceClientGtk', '3.0')
gi.require_version('Gtk', '3.0')
from gi.repository import SpiceClientGtk, SpiceClientGLib, GObject, Gtk

class Viewer(GObject.GObject):

    __gsignals__ = {
        "add-display-widget": (GObject.SignalFlags.RUN_FIRST, None, [object]),
        "size-allocate": (GObject.SignalFlags.RUN_FIRST, None, [object]),
        "focus-in-event": (GObject.SignalFlags.RUN_FIRST, None, [object]),
        "focus-out-event": (GObject.SignalFlags.RUN_FIRST, None, [object]),
        "pointer-grab": (GObject.SignalFlags.RUN_FIRST, None, []),
        "pointer-ungrab": (GObject.SignalFlags.RUN_FIRST, None, []),
        "connected": (GObject.SignalFlags.RUN_FIRST, None, []),
        "disconnected": (GObject.SignalFlags.RUN_FIRST, None, [str, str]),
        "auth-error": (GObject.SignalFlags.RUN_FIRST, None, [str, bool]),
        "auth-rejected": (GObject.SignalFlags.RUN_FIRST, None, [str]),
        "need-auth": (GObject.SignalFlags.RUN_FIRST, None, [bool, bool]),
        "agent-connected": (GObject.SignalFlags.RUN_FIRST, None, []),
        "grab-keys-pressed": (GObject.SignalFlags.RUN_FIRST, None, []),
    }

    def __init__(self, info):
        GObject.GObject.__init__(self)
        self._display = None
        self._info = info # 服务器信息

    # 建立与服务器的会话连接
    def open(self):
        fd = self._get_fd_for_open()
        if fd is not None:
            self._open_fd(fd)
        else:
            self._open_host()

    # 关闭与服务器的会话连接
    def close(self):
        raise NotImplementedError()

    # 获取用户创建的与服务器的套接字连接
    def _get_fd_for_open(self):
        None

    def remove_display_widget(self, widget):
        if self._display and self._display in widget.get_children():
            widget.remove(self._display)

    #######################################################

    # Internal API that will be overwritten by subclasses #

    #######################################################

    # 使用用户创建的套接字来建立会话连接
    def _open_fd(self, fd):
        raise NotImplementedError()

    # 使用连接协议自动创建的套接字来建立会话连接
    def _open_host(self):
        raise NotImplementedError()

class SpiceViewer(Viewer):

    def __init__(self, *args, **kwargs):
        Viewer.__init__(self, *args, **kwargs)
        self._spice_session = None

    def close(self):
        if self._display:
            self._display.destroy()
        self._spice_session.disconnect()

    def _open_fd(self, fd):
        self._create_session()
        self._spice_session.open_fd(fd)

    def _open_host(self):
        self._create_session()
        host, port, tlsport = self._info.get_conn_host()
        self._spice_session.set_property("host", str(host))
        if port:
            self._spice_session.set_property("port", str(port))
        if tlsport:
            self._spice_session.set_property("tls-port", str(tlsport))
        self._spice_session.connect()

    # 创建spice会话对象
    def _create_session(self):
        self._spice_session = SpiceClientGLib.Session.new()
        GObject.GObject.connect(self._spice_session, "channel-new", self._channel_new_cb)

    # channel创建信号回调函数
    def _channel_new_cb(self, session, channel):
        GObject.GObject.connect(channel, "open-fd", self._channel_open_fd_cb)

        if (type(channel) == SpiceClientGLib.MainChannel):
            GObject.GObject.connect(channel, "channel-event", self._channel_event_cb)
        elif (type(channel) == SpiceClientGLib.DisplayChannel and not self._display):
            # 创建显示部件
            channel_id = channel.get_property("channel-id")
            self._display = SpiceClientGtk.Display.new(session, channel_id)
            self.emit("add-display-widget", self._display)
            self._display.realize()
            self._display.connect("mouse-grab", self._mouse_grab_cb)
            self._display.connect("grab-keys-pressed", self._grab_keys_pressed_cb)
            self._display.show()
            channel.connect()
        elif (type(channel) == SpiceClientGLib.InputsChannel):
            None
        elif (type(channel) == SpiceClientGLib.PlaybackChannel):
            None

    # channel关联套接字句柄信号回调函数,当channel.connect()发现spice会话对象没有可用的套接字句柄时会触发此信号
    def _channel_open_fd_cb(self, channel, with_tls):
        None

    # channel事件信号回调函数
    def _channel_event_cb(self, channel, event):
        if event == SpiceClientGLib.ChannelEvent.CLOSED:
            self.emit("disconnected", None, None)
        elif event == SpiceClientGLib.ChannelEvent.ERROR_AUTH:
            if not self._spice_session.get_property("password"):
                self.emit("need-auth", True, False)
            else:
                self.emit("auth-error", channel.get_error().message, False)
        elif "ERROR" in str(event):
            self.emit("disconnected", channel.get_error().message, None)

    # 鼠标捕获信号回调函数
    def _mouse_grab_cb(self, display, status):
        if status:
            self.emit("pointer-grab")
        else:
            self.emit("pointer-ungrab")

    # 捕获键按下信号回调函数
    def _grab_keys_pressed_cb(self, display):
        self.emit("grab-keys-pressed")

main.py文件

# -*- coding: utf-8 -*-

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk,GObject
from viewers import SpiceViewer

class HostInfo():
    def __init__(self):
        self.gaddr = "127.0.0.1"
        self.gport = 5900
        self.gtlsport = None

    def get_conn_host(self):
        host = self.gaddr
        port = self.gport
        tlsport = self.gtlsport
        return host, port, tlsport

def add_display_widget_cb(viewer, display):
    win.add(display)
    win.fullscreen()

def grab_keys_pressed_cb(viewer):
    win.unfullscreen()

win = Gtk.Window(title="Spicy")
info = HostInfo()
viewer = SpiceViewer(info)
viewer.open()
viewer.connect("add-display-widget", add_display_widget_cb)
viewer.connect("grab-keys-pressed", grab_keys_pressed_cb)
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()

转载于:https://www.cnblogs.com/silvermagic/p/7666216.html

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spice-gtk 是一个开源的用于远程访问和显示虚拟化环境的客户端库,它允许用户通过网络连接到远程虚拟机,并在本地进行交互操作和图形渲染。下面是对其源码的简要分析。 首先,spice-gtk 的源码采用 C 语言编写,为构建虚拟化环境提供了一个简洁而高效的框架。它使用了许多不同的库和工具,如 GTK+,Glib 和 Cairo,用于实现图形用户界面以及输入和输出设备的处理。 源码中的主要组件包括 SPICE Client,用于建立与远程虚拟机的通信连接,并处理输入和输出数据流。它封装了 SPICE 协议,以实现远程图形渲染和输入事件传递。另一个重要的组件是 SPICE Display,用于渲染和显示远程虚拟机的图像内容。 在源码中,还可以找到一些辅助模块,如音频处理模块、USB 设备管理模块等。这些模块负责管理与虚拟机相关的附加功能,并提供了相应的接口。 在分析源码时,我们可以看到源码中使用了许多设计模式和技术,如单例模式、事件驱动模型和异步处理。这些设计模式和技术的使用使得源码具有扩展性和可维护性,为开发人员提供了自定义和扩展的便利。 总之,spice-gtk 的源码分析涉及到底层通信协议的实现、图形渲染和输入输出处理等方面。通过深入研究和分析,可以更好地理解 spic-gtk 的工作原理,并为其开发和维护提供指导和支持。 ### 回答2: spice-gtk是一个开源的包含SPICE客户端的GTK+工具包,用于与SPICE协议兼容的远程计算机进行交互和图形渲染。它的源代码可以帮助我们了解SPICE协议的实现细节以及与远程计算机通信的方式。 spice-gtk的源码是使用C语言编写的,主要由几个模块和文件组成。其中最重要的模块是spice-client,它实现了SPICE协议的客户端功能。此外,还有其他辅助模块,如spice-widget和spice-common,它们提供了用于创建GUI界面和共享资源的工具。 在源码中,我们可以找到与SPICE协议相关的数据结构和函数。这些数据结构用于存储协议消息的信息,例如输入事件、帧缓冲和图像数据等。函数则用于处理这些消息,完成数据的编解码、传输和渲染等操作。 通过分析源码,我们可以深入了解SPICE协议的各个阶段和步骤。例如,源码中可能包含与连接建立和认证相关的代码,用于验证客户端与远程计算机之间的身份和权限。同时,我们还可以找到与图形渲染和输入事件处理相关的代码,用于将远程计算机的图像和用户的输入交互传递。此外,源码中可能还包含了与音频、剪贴板共享和本地文件传输等功能相关的代码。 总之,通过对spice-gtk源码的分析,我们可以了解SPICE协议的实现原理以及与远程计算机通信的细节。这有助于我们深入理解SPICE技术,并能够根据需求进行自定义修改和扩展。同时,也可以从中学习到一些关于C语言编程和GTK+工具包的技巧和经验。 ### 回答3: spice-gtk是一款用于远程桌面应用程序的开源工具包。它提供了一组用于在本地计算机上创建与远程服务器连接的库和工具。 对于spice-gtk源代码的分析,我们可以从以下几个方面着手: 1. 连接管理:spice-gtk提供了连接管理的功能,用于建立与远程服务器的连接。可以分析其网络编程相关的代码,包括套接字通信、连接建立和认证等过程。 2. 图形渲染:spice-gtk可以将远程服务器的图像数据渲染到本地计算机上。源代码中可能包含与图像编解码、颜色管理和图像渲染相关的实现细节。 3. 输入设备:spice-gtk允许用户在本地计算机上控制远程服务器。源代码中可能包含处理本地输入设备(例如鼠标和键盘)事件的代码,以及将这些事件传递到远程服务器的实现部分。 4. 窗口管理:spice-gtk还提供了窗口管理的功能,用于在本地计算机上显示远程服务器的应用程序窗口。源代码可能包含与窗口管理相关的实现,例如窗口布局、窗口管理器通信等。 5. 性能优化:在源代码中可能会涉及性能优化的实现细节,例如数据压缩算法、带宽管理、缓存策略等。分析这些部分可以了解spice-gtk如何提高远程桌面应用程序的性能和响应速度。 总体而言,对于spice-gtk源代码的分析需要关注网络通信、图形渲染、输入设备、窗口管理和性能优化等方面的实现细节。深入理解这些细节有助于我们更好地使用和定制spice-gtk,并为远程桌面应用程序的开发和优化提供指导。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值