diff --git a/draft/test.py b/draft/test.py index 84e00cf..c05cd69 100755 --- a/draft/test.py +++ b/draft/test.py @@ -6,6 +6,9 @@ import gi, cairo gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk, GdkPixbuf + +from utils import blur_image + # 获取默认显示器 dp = Gdk.Display.get_default() @@ -43,6 +46,8 @@ def on_mouse_move(widget, event): class App(Gtk.Window): + saved = False + def __init__(self): Gtk.Window.__init__(self, type=Gtk.WindowType.POPUP) @@ -54,26 +59,25 @@ class App(Gtk.Window): # 设置透明背景 screen = self.get_screen() visual = screen.get_rgba_visual() - # if visual and screen.is_composited(): - # self.set_visual(visual) + if visual and screen.is_composited(): + self.set_visual(visual) - # self.set_app_paintable(True) + self.set_app_paintable(True) + self.set_focus() - self.set_events(Gdk.EventMask.POINTER_MOTION_MASK) + # self.set_events(Gdk.EventMask.POINTER_MOTION_MASK) # window.connect("button-press-event", on_mouse_move) # window.connect("button-release-event", on_mouse_move) # window.connect("motion-notify-event", on_mouse_move) # window.connect("motion-notify-event", on_mouse_move) - # layout = Gtk.Fixed() - # btn = Gtk.Button(label = 'hello world') - # btn.set_size_request(200, 32) - # layout.put(btn, 0, 0) + layout = Gtk.Fixed() + # self.add(layout) - layout = Gtk.Fixed() + # layout = Gtk.Fixed() self.drawing_area = Gtk.DrawingArea() self.drawing_area.set_size_request(total_width, total_height) @@ -85,7 +89,11 @@ class App(Gtk.Window): self.drawing_area.connect("motion-notify-event", self.on_motion_notify) layout.add(self.drawing_area) + btn = Gtk.Button(label = 'hello world') + btn.set_size_request(200, 60) + layout.put(btn, 200, 100) + self.layout = layout self.add(layout) # Initialize rectangle and control point size self.start_x = None @@ -96,7 +104,26 @@ class App(Gtk.Window): self.dragging = False self.resize_dragging = False - self.connect("draw", on_draw) + self.connect("draw", self.on_before_start) + self.connect("delete-event", Gtk.main_quit) + self.connect("key-press-event", self.on_key_down) + + + + def on_before_start(self, win, cr): + + if not self.saved: + # print('又进来了') + window = Gdk.get_default_root_window() + width, height = window.get_width(), window.get_height() + self.entire_desktop = Gdk.pixbuf_get_from_window(window, 0, 0, width, height) + # self.entire_desktop.savev("entire_desktop.png", "png", [], []) + self.saved = True + + cr.set_source_rgba(0, 0, 0, 0.3) # 设置颜色和透明度 + cr.set_operator(cairo.OPERATOR_SOURCE) + cr.paint() + cr.set_operator(cairo.OPERATOR_OVER) @@ -104,12 +131,27 @@ class App(Gtk.Window): if self.start_x is not None and self.start_y is not None: width = self.current_x - self.start_x height = self.current_y - self.start_y + + # print(abs(width), abs(height)) + + # 白色 + # cr.set_source_rgb(1, 1, 1) + + cr.set_source_rgb(208/255, 234/255, 1) + cr.set_line_width(2) + + cr.set_dash([6.0, 4.0]) cr.rectangle(self.start_x, self.start_y, width, height) cr.stroke() + center = (self.start_x + width / 2, self.start_y + height / 2) # draw resizing dots - for x in [self.start_x, self.start_x + width / 2, self.current_x]: - for y in [self.start_y, self.start_y + height / 2, self.current_y]: + for x in [self.start_x, center[0], self.current_x]: + for y in [self.start_y, center[1], self.current_y]: + # 中心的圆点不画 + if x == center[0] and y == center[1]: + continue + cr.arc(x, y, 5, 0, 2 * 3.14) cr.fill() @@ -126,6 +168,7 @@ class App(Gtk.Window): def on_button_release(self, widget, event): if event.button == 1: # left mouse button self.dragging = False + self.screenshot() self.queue_draw() @@ -136,6 +179,26 @@ class App(Gtk.Window): self.queue_draw() + def screenshot(self): + x = self.start_x + y = self.start_y + w = self.current_x - x + h = self.current_y - y + pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, abs(w), abs(h)) + self.entire_desktop.copy_area(x, y, w, h, pixbuf, 0, 0) + + # pixbuf = blur_image(pixbuf) + # pixbuf.savev("screenshot.png", "png", [], []) + img = Gtk.Image() + img.set_from_pixbuf(pixbuf) + self.layout.put(img, x + 1, y + 1) + self.layout.show_all() + + + def on_key_down(self, win, ev): + if ev.keyval == Gdk.KEY_Escape: + Gtk.main_quit() + def run(self): try: diff --git a/draft/utils.py b/draft/utils.py new file mode 100644 index 0000000..e265ded --- /dev/null +++ b/draft/utils.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + + +import gi, io, base64, threading +from gi.repository import Gdk, GLib, GdkPixbuf, Gio, GObject +from PIL import Image, ImageFilter + + +empty_pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 1, 1) + + +def pixbuf_to_pil(pixbuf): + data = pixbuf.get_pixels() + w = pixbuf.get_width() + h = pixbuf.get_height() + stride = pixbuf.get_rowstride() + mode = "RGBA" if pixbuf.get_has_alpha() else "RGB" + img = Image.frombytes(mode, (w, h), data, "raw", mode, stride) + return img + + +def pil_to_pixbuf(img): + data = [] + for p in img.getdata(): + data.extend(p) + data = bytes(data) + alpha = img.mode == 'RGBA' + w, h = img.size + rowstride = w * 4 + try: + return GdkPixbuf.Pixbuf.new_from_bytes(GLib.Bytes.new(data), GdkPixbuf.Colorspace.RGB, alpha, 8, w, h, rowstride) + except: + return empty_pixbuf + + +def pic_to_pixbuf(pic): + data = pic.data + input_stream = Gio.MemoryInputStream.new_from_data(data, None) + try: + return GdkPixbuf.Pixbuf.new_from_stream(input_stream, None) + except: + return empty_pixbuf + +def base64_to_pixbuf(base64_str): + data = base64.b64decode(base64_str) + input_stream = Gio.MemoryInputStream.new_from_data(data, None) + try: + return GdkPixbuf.Pixbuf.new_from_stream(input_stream, None) + except: + return empty_pixbuf + + +def blur_image(pixbuf): + # 加载图片并确认该图片为RGBA模式,保证透明度 + img = pixbuf_to_pil(pixbuf).convert('RGBA') + mask = Image.new('RGBA', img.size, (64, 64, 64, 0)) + img = img.filter(ImageFilter.GaussianBlur(radius = 6)) + img.alpha_composite(mask) + + return pil_to_pixbuf(img) + + + +# 定义一个异步修饰器, 用于在子线程中运行一些会阻塞主线程的任务 +def run_async(func): + def wrapper(*args, **kwargs): + thread = threading.Thread(target=func, args=args, kwargs=kwargs) + thread.daemon = True + thread.start() + return thread + return wrapper + +# 类型js的settimeout的修饰器 +def set_timeout(timeout = 0.5): + def decorator(callback): + def wrapper(*args): + t = threading.Timer(timeout, callback, args=args) + t.start() + return t + return wrapper + + return decorator + + +# 定义一个修饰器, 用于将当前方法转到主线程中运行 (子线程中调用方法时) +def idle(func): + def wrapper(*args): + GObject.idle_add(func, *args) + return wrapper + diff --git a/usr/lib/xshot/shot_the_window.py b/usr/lib/xshot/shot_the_window.py new file mode 100755 index 0000000..2a7de48 --- /dev/null +++ b/usr/lib/xshot/shot_the_window.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 + + +import gi, sys, os, cairo, math +import dbus +import dbus.service, dbus.mainloop.glib + +gi.require_version('Gtk', '3.0') + +from gi.repository import Gtk, Gdk, GLib, GdkPixbuf +from PIL import Image, ImageFilter, ImageChops + +app_id = 'fun.wkit.xshot' + + +def pixbuf_to_pil(pixbuf): + data = pixbuf.get_pixels() + w = pixbuf.get_width() + h = pixbuf.get_height() + stride = pixbuf.get_rowstride() + mode = "RGBA" if pixbuf.get_has_alpha() else "RGB" + img = Image.frombytes(mode, (w, h), data, "raw", mode, stride) + return img + + +def pil_to_pixbuf(img): + data = [] + for p in img.getdata(): + data.extend(p) + data = bytes(data) + alpha = img.mode == 'RGBA' + w, h = img.size + rowstride = w * 4 + return GdkPixbuf.Pixbuf.new_from_bytes(GLib.Bytes.new(data), GdkPixbuf.Colorspace.RGB, alpha, 8, w, h, rowstride) + + +def set_radius(pixbuf, radius = 4): + w = pixbuf.get_width() + h = pixbuf.get_height() + + surface = cairo.ImageSurface(cairo.Format.ARGB32, w, h) + ctx = cairo.Context(surface) + Gdk.cairo_set_source_pixbuf(ctx, pixbuf, 0, 0) + + # 左上角 圆角 + ctx.arc(radius, radius, radius, -math.pi, -math.pi / 2) + ctx.line_to(w - radius, 0) + + # 右上角 圆角 + ctx.arc(w - radius, radius, radius, -math.pi / 2, 0) + ctx.line_to(w, -radius) + + # 右下角 圆角 + ctx.arc(w - radius, h - radius, radius, 0, math.pi / 2) + ctx.line_to(radius, h) + + # 左下角 圆角 + ctx.arc(radius, h - radius, radius, math.pi / 2, math.pi) + ctx.close_path() + + ctx.clip() + ctx.paint() + + return Gdk.pixbuf_get_from_surface(surface, 0, 0, w, h) + + + +def add_shadow(img): + # 加载图片并确认该图片为RGBA模式,保证透明度 + img = set_radius(img) + img = pixbuf_to_pil(img).convert('RGBA') + w,h = img.size + + w += 64 + h += 64 + + shadow = Image.new('RGBA', img.size, (80,80,80,223)) + shadow = pixbuf_to_pil(set_radius(pil_to_pixbuf(shadow))) + + #new_img = Image.new('RGBA', (w,h), (0,0,0,0)) + new_img = Image.new('RGBA', (w,h), (255,255,255,255)) + new_img.paste(shadow, (32, 32), shadow) + + new_img = new_img.filter(ImageFilter.GaussianBlur(radius = 12)) + new_img.paste(img, (32, 32), img) + + return pil_to_pixbuf(new_img) + + +class Application(Gtk.Application): + def __init__(self): + Gtk.Application.__init__(self, application_id = app_id) + + display = Gdk.Display.get_default() + self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) + + + def do_activate(self): + self.shot_the_window() + + + def copy(self, image): + + # 写图片进剪切板 + self.clipboard.clear() + self.clipboard.set_image(image) + self.clipboard.store() + + + def shot_the_window(self): + + screen = Gdk.Screen.get_default() + win = screen.get_active_window() + + width = win.get_width() + height = win.get_height() + + # 获取窗口图像 + image = Gdk.pixbuf_get_from_window(win, 0, 0, width, height) + image = add_shadow(image) + + self.copy(image) + +class ApplicationService(dbus.service.Object): + def __init__(self, app): + self.app = app + bus_name = dbus.service.BusName(app_id, bus = dbus.SessionBus()) + dbus.service.Object.__init__(self, bus_name, '/') + + + @dbus.service.method(app_id) + def call_app(self): + self.app.shot_the_window() + + +if __name__ == "__main__": + dbus.mainloop.glib.DBusGMainLoop(set_as_default = True) + bus = dbus.SessionBus() + + try: + obj = bus.get_object(app_id, '/') + obj.call_app() + sys.exit(0) + except dbus.DBusException: + pass + + + app = Application() + app.run(sys.argv) + + ApplicationService(app) + + Gtk.main() +