python实现桌面气泡提示功能


Posted in Python onJuly 29, 2019

在写桌面软件时,通常会使用到托盘上的泡泡提示功能,让我们来看看使用python如何实现这个小功能。

一、Linux系统

在Linux上,实现一个气泡提示非常简单,使用GTK实现的pynotify模块提供了些功能,我的环境是Ubuntu,默认安装此模块,如果没有,下载源文件编译安装一个。实现代码如下:

#!/usr/bin/python
#coding:utf-8
 
import pynotify
 
pynotify.init ("Bubble@Linux")
bubble_notify = pynotify.Notification ("Linux上的泡泡提示", "看,比Windows上实现方便多了!")
bubble_notify.show ()

效果:

python实现桌面气泡提示功能

二、Windows下的实现

Windows下实现是比较复杂的,没有pynotify这样一个模块,找到了一个还算不错的模块(地址),这个类有些语法上的小问题,至少在python2.6下如此,需要修改一下,如下是修改后的代码),基本可用,代码如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
#gtkPopupNotify.py
#
# Copyright 2009 Daniel Woodhouse 
# modified by NickCis 2010 http://github.com/NickCis/gtkPopupNotify
# Modifications:
#     Added: * Corner support (notifications can be displayed in all corners
#        * Use of gtk Stock items or pixbuf to render images in notifications
#        * Posibility of use fixed height
#        * Posibility of use image as background
#        * Not displaying over Windows taskbar(taken from emesene gpl v3)
#        * y separation.
#        * font description options
#        * Callbacks For left, middle and right click
#
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Lesser General Public License as published by
#the Free Software Foundation, either version 3 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 Lesser General Public License for more details.
#
#You should have received a copy of the GNU Lesser General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
 
 
import os
import gtk
import pango
import gobject
 
# This code is used only on Windows to get the location on the taskbar
# Taken from emesene Notifications (Gpl v3)
taskbarOffsety = 0
taskbarOffsetx = 0
if os.name == "nt":
  import ctypes
  from ctypes.wintypes import RECT, DWORD
  user = ctypes.windll.user32
  MONITORINFOF_PRIMARY = 1
  HMONITOR = 1
 
  class MONITORINFO(ctypes.Structure):
    _fields_ = [
      ('cbSize', DWORD),
      ('rcMonitor', RECT),
      ('rcWork', RECT),
      ('dwFlags', DWORD)
      ]
 
  taskbarSide = "bottom"
  taskbarOffset = 30
  info = MONITORINFO()
  info.cbSize = ctypes.sizeof(info)
  info.dwFlags = MONITORINFOF_PRIMARY
  user.GetMonitorInfoW(HMONITOR, ctypes.byref(info))
  if info.rcMonitor.bottom != info.rcWork.bottom:
    taskbarOffsety = info.rcMonitor.bottom - info.rcWork.bottom
  if info.rcMonitor.top != info.rcWork.top:
    taskbarSide = "top"
    taskbarOffsety = info.rcWork.top - info.rcMonitor.top
  if info.rcMonitor.left != info.rcWork.left:
    taskbarSide = "left"
    taskbarOffsetx = info.rcWork.left - info.rcMonitor.left
  if info.rcMonitor.right != info.rcWork.right:
    taskbarSide = "right"
    taskbarOffsetx = info.rcMonitor.right - info.rcWork.right
 
class NotificationStack:
  
  def __init__(self, size_x=300, size_y=-1, timeout=5, corner=(False, False), sep_y=0):
    """
    Create a new notification stack. The recommended way to create Popup instances.
     Parameters:
      `size_x` : The desired width of the notifications.
      `size_y` : The desired minimum height of the notifications. If it isn't set,
      or setted to None, the size will automatically adjust
      `timeout` : Popup instance will disappear after this timeout if there
      is no human intervention. This can be overridden temporarily by passing
      a new timout to the new_popup method.
      `coner` : 2 Value tuple: (true if left, True if top)
      `sep_y` : y distance to separate notifications from each other
    """
    self.size_x = size_x
    self.size_y = -1 
    if (size_y == None): 
      pass
    else:
       size_y
    self.timeout = timeout
    self.corner = corner
    self.sep_y = sep_y
    """
    Other parameters:
    These will take effect for every popup created after the change.
      
      `edge_offset_y` : distance from the bottom of the screen and
      the bottom of the stack.
      `edge_offset_x` : distance from the right edge of the screen and
      the side of the stack.
      `max_popups` : The maximum number of popups to be shown on the screen
      at one time.
      `bg_color` : if None default is used (usually grey). set with a gtk.gdk.Color.
      `bg_pixmap` : Pixmap to use as background of notification. You can set a gtk.gdk.Pixmap
      or a path to a image. If none, the color background will be displayed.
      `bg_mask` : If a gtk.gdk.pixmap is specified under bg_pixmap, the mask of the pixmap has to be setted here.
      `fg_color` : if None default is used (usually black). set with a gtk.gdk.Color.
      `show_timeout` : if True, a countdown till destruction will be displayed.
      `close_but` : if True, the close button will be displayed.
      `fontdesc` : a 3 value Tuple containing the pango.FontDescriptions of the Header, message and counter
       (in that order). If a string is suplyed, it will be used for the 3 the same FontDescription.
       http://doc.stoq.com.br/devel/pygtk/class-pangofontdescription.html
    """    
    self.edge_offset_x = 0
    self.edge_offset_y = 0
    self.max_popups = 5
    self.fg_color = None
    self.bg_color = None
    self.bg_pixmap = None
    self.bg_mask = None
    self.show_timeout = False
    self.close_but = True
    self.fontdesc = ("Sans Bold 14", "Sans 12", "Sans 10")
    
    self._notify_stack = []
    self._offset = 0
 
    
  def new_popup(self, title, message, image=None, leftCb=None, middleCb=None, rightCb=None):
    """Create a new Popup instance."""
    if len(self._notify_stack) == self.max_popups:
      self._notify_stack[0].hide_notification()
    self._notify_stack.append(Popup(self, title, message, image, leftCb, middleCb, rightCb))
    self._offset += self._notify_stack[-1].y
    
  def destroy_popup_cb(self, popup):
    self._notify_stack.remove(popup)
    #move popups down if required
    offset = 0
    for note in self._notify_stack:
      offset = note.reposition(offset, self)
    self._offset = offset
  
  
 
  
class Popup(gtk.Window):
  def __init__(self, stack, title, message, image, leftCb, middleCb, rightCb):
    gtk.Window.__init__(self, type=gtk.WINDOW_POPUP)
    
    self.leftclickCB = leftCb
    self.middleclickCB = middleCb
    self.rightclickCB = rightCb    
    
    self.set_size_request(stack.size_x, stack.size_y)
    self.set_decorated(False)
    self.set_deletable(False)
    self.set_property("skip-pager-hint", True)
    self.set_property("skip-taskbar-hint", True)
    self.connect("enter-notify-event", self.on_hover, True)
    self.connect("leave-notify-event", self.on_hover, False)
    self.set_opacity(0.2)
    self.destroy_cb = stack.destroy_popup_cb
    
    if type(stack.fontdesc) == tuple or type(stack.fontdesc) == list:
      fontH, fontM, fontC = stack.fontdesc
    else:
      fontH = fontM = fontC = stack.fontdesc
    
    main_box = gtk.VBox()
    header_box = gtk.HBox()
    self.header = gtk.Label()
    self.header.set_markup("<b>%s</b>" % title)
    self.header.set_padding(3, 3)
    self.header.set_alignment(0, 0)
    try:
      self.header.modify_font(pango.FontDescription(fontH))
    except Exception, e:
      print e
    header_box.pack_start(self.header, True, True, 5)
    if stack.close_but:
      close_button = gtk.Image()
    
      close_button.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
      close_button.set_padding(3, 3)
      close_window = gtk.EventBox()
      close_window.set_visible_window(False)
      close_window.connect("button-press-event", self.hide_notification)
      close_window.add(close_button)
      header_box.pack_end(close_window, False, False)
    main_box.pack_start(header_box)
    
    body_box = gtk.HBox()
    if image is not None:
      self.image = gtk.Image()
      self.image.set_size_request(70, 70)
      self.image.set_alignment(0, 0)
      if image in gtk.stock_list_ids():
        self.image.set_from_stock(image, gtk.ICON_SIZE_DIALOG)
      elif type(image) == gtk.gdk.Pixbuf:
        self.image.set_from_pixbuf(image)
      else:
        self.image.set_from_file(image)
      body_box.pack_start(self.image, False, False, 5)
    self.message = gtk.Label()
    self.message.set_property("wrap", True)
    self.message.set_size_request(stack.size_x - 90, -1)
    self.message.set_alignment(0, 0)
    self.message.set_padding(5, 10)
    self.message.set_markup(message)
    try:
      self.message.modify_font(pango.FontDescription(fontM))
    except Exception, e:
      print e
    self.counter = gtk.Label()
    self.counter.set_alignment(1, 1)
    self.counter.set_padding(3, 3)
    try:
      self.counter.modify_font(pango.FontDescription(fontC))
    except Exception, e:
      print e
    self.timeout = stack.timeout
    
    body_box.pack_start(self.message, True, False, 5)
    body_box.pack_end(self.counter, False, False, 5)
    main_box.pack_start(body_box)
    eventbox = gtk.EventBox()
    eventbox.set_property('visible-window', False)
    eventbox.set_events(gtk.gdk.BUTTON_PRESS_MASK)
    eventbox.connect("button_press_event", self.onClick) 
    eventbox.add(main_box)
    self.add(eventbox)
    if stack.bg_pixmap is not None:
      if not type(stack.bg_pixmap) == gtk.gdk.Pixmap:
        stack.bg_pixmap, stack.bg_mask = gtk.gdk.pixbuf_new_from_file(stack.bg_pixmap).render_pixmap_and_mask()
      self.set_app_paintable(True)
      self.connect_after("realize", self.callbackrealize, stack.bg_pixmap, stack.bg_mask)
    elif stack.bg_color is not None:
      self.modify_bg(gtk.STATE_NORMAL, stack.bg_color)
    if stack.fg_color is not None:
      self.message.modify_fg(gtk.STATE_NORMAL, stack.fg_color)
      self.header.modify_fg(gtk.STATE_NORMAL, stack.fg_color)
      self.counter.modify_fg(gtk.STATE_NORMAL, stack.fg_color)
    self.show_timeout = stack.show_timeout
    self.hover = False
    self.show_all()
    self.x, self.y = self.size_request()
    #Not displaying over windows bar 
    if os.name == 'nt':
      if stack.corner[0] and taskbarSide == "left":
        stack.edge_offset_x += taskbarOffsetx
      elif not stack.corner[0] and taskbarSide == 'right':
        stack.edge_offset_x += taskbarOffsetx
      if stack.corner[1] and taskbarSide == "top":
        stack.edge_offset_x += taskbarOffsety
      elif not stack.corner[1] and taskbarSide == 'bottom':
        stack.edge_offset_x += taskbarOffsety
        
    if stack.corner[0]:
      posx = stack.edge_offset_x
    else:
      posx = gtk.gdk.screen_width() - self.x - stack.edge_offset_x
    sep_y = 0 
    if (stack._offset == 0):
      pass
    else:
       stack.sep_y
    self.y += sep_y
    if stack.corner[1]:
      posy = stack._offset + stack.edge_offset_y + sep_y
    else:
      posy = gtk.gdk.screen_height()- self.y - stack._offset - stack.edge_offset_y
    self.move(posx, posy)
    self.fade_in_timer = gobject.timeout_add(100, self.fade_in)
    
    
 
  def reposition(self, offset, stack):
    """Move the notification window down, when an older notification is removed"""
    if stack.corner[0]:
      posx = stack.edge_offset_x
    else:
      posx = gtk.gdk.screen_width() - self.x - stack.edge_offset_x
    if stack.corner[1]:
      posy = offset + stack.edge_offset_y
      new_offset = self.y + offset
    else:      
      new_offset = self.y + offset
      posy = gtk.gdk.screen_height() - new_offset - stack.edge_offset_y + stack.sep_y
    self.move(posx, posy)
    return new_offset
 
  
  def fade_in(self):
    opacity = self.get_opacity()
    opacity += 0.15
    if opacity >= 1:
      self.wait_timer = gobject.timeout_add(1000, self.wait)
      return False
    self.set_opacity(opacity)
    return True
      
  def wait(self):
    if not self.hover:
      self.timeout -= 1
    if self.show_timeout:
      self.counter.set_markup(str("<b>%s</b>" % self.timeout))
    if self.timeout == 0:
      self.fade_out_timer = gobject.timeout_add(100, self.fade_out)
      return False
    return True
   
  
  def fade_out(self):
    opacity = self.get_opacity()
    opacity -= 0.10
    if opacity <= 0:
      self.in_progress = False
      self.hide_notification()
      return False
    self.set_opacity(opacity)
    return True
  
  def on_hover(self, window, event, hover):
    """Starts/Stops the notification timer on a mouse in/out event"""
    self.hover = hover
 
    
  def hide_notification(self, *args):
    """Destroys the notification and tells the stack to move the
    remaining notification windows"""
    for timer in ("fade_in_timer", "fade_out_timer", "wait_timer"):
      if hasattr(self, timer):
        gobject.source_remove(getattr(self, timer))
    self.destroy()
    self.destroy_cb(self)
 
  def callbackrealize(self, widget, pixmap, mask=False):
    #width, height = pixmap.get_size()
    #self.resize(width, height)
    if mask is not False:
      self.shape_combine_mask(mask, 0, 0)
    self.window.set_back_pixmap(pixmap, False)
    return True
 
  def onClick(self, widget, event):
    if event.button == 1 and self.leftclickCB != None:
      self.leftclickCB()
      self.hide_notification()
    if event.button == 2 and self.middleclickCB != None:
      self.middleclickCB()
      self.hide_notification()
    if event.button == 3 and self.rightclickCB != None:
      self.rightclickCB()
      self.hide_notification()
 
if __name__ == "__main__":
  #example usage
  
  def notify_factory():
    color = ("green", "blue")
    image = "logo1_64.png"
    notifier.bg_color = gtk.gdk.Color(color[0])
    notifier.fg_color = gtk.gdk.Color(color[1])
    notifier.show_timeout = True 
    notifier.edge_offset_x = 20
    notifier.edge_offset_y = 30
    notifier.new_popup("Windows上的泡泡提示", "NND,比Linux下复杂多了,效果还不怎么样", image=image)
    return True
 
  def gtk_main_quit():
    print "quitting"
    gtk.main_quit()
  
  notifier = NotificationStack(timeout=1) 
  gobject.timeout_add(4000, notify_factory)
  gobject.timeout_add(8000, gtk_main_quit)
  gtk.main()

效果如下:

python实现桌面气泡提示功能

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python内置的HTTP协议服务器SimpleHTTPServer使用指南
Mar 30 Python
python并发2之使用asyncio处理并发
Dec 21 Python
scrapy爬虫完整实例
Jan 25 Python
python读取和保存视频文件
Apr 16 Python
浅谈Pandas 排序之后索引的问题
Jun 07 Python
python3+pyqt5+itchat微信定时发送消息的方法
Feb 20 Python
python字符串查找函数的用法详解
Jul 08 Python
python按键按住不放持续响应的实例代码
Jul 17 Python
Django框架实现在线考试系统的示例代码
Nov 30 Python
python palywright库基本使用
Jan 21 Python
关于python中模块和重载的问题
Nov 02 Python
Python测试框架pytest高阶用法全面详解
Jun 01 Python
pycharm设置鼠标悬停查看方法设置
Jul 29 #Python
django rest framework vue 实现用户登录详解
Jul 29 #Python
python实现倒计时小工具
Jul 29 #Python
django rest framework 实现用户登录认证详解
Jul 29 #Python
pycharm重命名文件的方法步骤
Jul 29 #Python
PyQt5实现暗黑风格的计时器
Jul 29 #Python
Python Django 实现简单注册功能过程详解
Jul 29 #Python
You might like
php 字符串压缩方法比较示例
2014/01/23 PHP
PHP CURL获取返回值的方法
2014/05/04 PHP
thinkphp autoload 命名空间自定义 namespace
2015/07/17 PHP
php猜单词游戏
2015/09/29 PHP
PHP中调用C/C++制作的动态链接库的教程
2016/03/10 PHP
PHP7生产环境队列Beanstalkd用法详解
2020/05/19 PHP
js GridView 实现自动计算操作代码
2009/03/25 Javascript
JavaScript高级程序设计 读书笔记之九 本地对象Array
2012/02/27 Javascript
JS事件Event元素(兼容IE,Firefox,Chorme)
2012/11/01 Javascript
js输入框邮箱自动提示功能代码实现
2013/12/10 Javascript
js获取当前地址 JS获取当前URL的示例代码
2014/02/26 Javascript
javascript回车完美实现tab切换功能
2014/03/13 Javascript
javascript设计模式之解释器模式详解
2014/06/05 Javascript
javascript实现验证IP地址等相关信息代码
2015/05/10 Javascript
基于jQuery下拉选择框插件支持单选多选功能代码
2016/06/07 Javascript
基于jQuery的checkbox全选问题分析
2016/11/18 Javascript
VUE使用vuex解决模块间传值问题的方法
2017/06/01 Javascript
红黑树的插入详解及Javascript实现方法示例
2018/03/26 Javascript
实例讲解Vue.js中router传参
2018/04/22 Javascript
详解vue beforeRouteEnter 异步获取数据给实例问题
2019/08/09 Javascript
jQuery实现回到顶部效果
2020/10/19 jQuery
[29:10]Ti4 冒泡赛第二天 NEWBEE vs Titan 3
2014/07/15 DOTA
python 生成不重复的随机数的代码
2011/05/15 Python
python下MySQLdb用法实例分析
2015/06/08 Python
Python实现快速排序算法及去重的快速排序的简单示例
2016/06/26 Python
Python中的命令行参数解析工具之docopt详解
2017/03/27 Python
Python实现删除文件中含“指定内容”的行示例
2017/06/09 Python
使用Python写一个量化股票提醒系统
2018/08/22 Python
python实现小程序推送页面收录脚本
2020/04/20 Python
python 8种必备的gui库
2020/08/27 Python
瑞士香水购物网站:Parfumcity.ch
2017/01/14 全球购物
阿联酋彩妆品牌:OUD MILANO
2019/10/06 全球购物
出纳会计岗位职责
2014/03/12 职场文书
安全标语口号
2014/06/09 职场文书
小区环境卫生倡议书
2015/04/29 职场文书
MySQL锁机制
2021/04/05 MySQL