通过Python调用Spice-gtk
序言
通过Virt Manager研究学习Spice gtk的Python方法
你将学到什么
Virt Manager研究
显示代码定位
首先我们使用Virt Manager来观察桌面连接窗口

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

接着我们在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

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

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()
通过Python调用Spice-gtk的更多相关文章
- 【初学python】使用python调用monkey测试
目前公司主要开发安卓平台的APP,平时测试经常需要使用monkey测试,所以尝试了下用python调用monkey,代码如下: import os apk = {'j': 'com.***.test1 ...
- python调用py中rar的路径问题。
1.python调用py,在py中的os.getcwd()获取的不是py的路径,可以通过os.path.split(os.path.realpath(__file__))[0]来获取py的路径. 2. ...
- python调用其他程序或脚本方法(转)
python运行(调用)其他程序或脚本 在Python中可以方便地使用os模块运行其他的脚本或者程序,这样就可以在脚本中直接使用其他脚本,或者程序提供的功能,而不必再次编写实现该功能的代码.为了更好地 ...
- python调用c\c++
前言 python 这门语言,凭借着其极高的易学易用易读性和丰富的扩展带来的学习友好性和项目友好性,近年来迅速成为了越来越多的人们的首选.然而一旦拿python与传统的编程语言(C/C++)如来比较的 ...
- Python调用C++
/***gcc -o libpycall.so -shared -fPIC pycall.c*/ #include <stdio.h> #include <stdlib.h> ...
- 使用Python调用Flickr API抓取图片数据
Flickr是雅虎旗下的图片分享网站,上面有全世界网友分享的大量精彩图片,被认为是专业的图片网站.其API也很友好,可以实现多种功能.这里我使用了Python调用其API获得了大量的照片数据.需要注意 ...
- python调用zabbix接口实现Action配置
要写这篇博客其实我的内心是纠结的,老实说,我对zabbix的了解实在不多.但新公司的需求不容置疑,当我顶着有两个头大的脑袋懵懵转入运维领域时,面前摆着两百多组.上千台机器等着写入zabbix监控的需求 ...
- linux下python调用c模块
在C调用Python模块时需要初始化Python解释器,导入模块等,但Python调用C模块却比较简单,下面还是以helloWorld.c 和 main.py 做一说明: (1)编写C代码,hel ...
- python 调用nmap
1.系统中需要安装nmap 2.系统中安装pip 2.安装python调用nmap的lib包 命令为:pip install python-nmap 以下是在centos系统下安装成功后的截图 在命令 ...
随机推荐
- ckeditor出现错误“从客户端(***)中检测到有潜在危险的 Request.Form值”的解决方法
ckeditor出现错误“从客户端(***)中检测到有潜在危险的 Request.Form值”的解决方法 页面中使用ckeditor,提交文章时总是出错,“从客户端(TextBox1="&l ...
- tomcat警告:Setting property 'source' to 'org.eclipse.jst.j2ee.server:ServletPro' did not find a matching property
警告: [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 'org.eclips ...
- 2018.5.28 PSOC第一枪:基于cypress的蓝牙开发
Cypress-BLE 开发套件可以快速开发 物联网电子产品. PSOC编程特点: A 拖放各PSoC 组件到工作区中,以设计原理图B 完成各组件之间的布线,并配置GPIOC 使用所包含的组件API ...
- L99
You're not obligated to win. You're obligated to keep trying.你不一定要获胜,但你必须不断尝试.He announced an expans ...
- A唐纳德先生和假骰子(华师网络赛)
Time limit per test: 1.0 seconds Memory limit: 256 megabytes 在进行某些桌游,例如 UNO 或者麻将的时候,常常会需要随机决定从谁开始.骰子 ...
- 2017-2018-1 20179215《Linux内核原理与分析》第八周作业
实验:ELF文件格式与程序的编译链接 一.可执行文件的创建 从源代码到可执行程序所要经历的过程概述: 源代码(.c .cpp .h)经过c预处理器(cpp)后生成.i文件,编译器(cc1.cc1p ...
- 【LeetCode】014. Longest Common Prefix
Write a function to find the longest common prefix string amongst an array of strings. 题解: 简单的暴力遍历解决 ...
- 浏览器全屏的JS代码实现
方法一:该方法是从一个网上的效果看到不错,然后自己就拿来下来实验了一下,还是比较满意度,下面直接给出代码 <!DOCTYPE HTML> <html lang="en-US ...
- POJ2442:Sequence
浅谈堆:https://www.cnblogs.com/AKMer/p/10284629.html 题目传送门:http://poj.org/problem?id=2442 我们先简化题意,假设只有两 ...
- BZOJ5289:[HNOI2018]排列
我对贪心的理解:https://www.cnblogs.com/AKMer/p/9776293.html 题目传送门:https://www.lydsy.com/JudgeOnline/problem ...