Add deiban, desktop dirs in order to build deb package.
12
Makefile.am
@ -15,8 +15,12 @@ if COMPILE_CLIENT
|
||||
MAKE_CLIENT = daemon
|
||||
endif
|
||||
|
||||
SUBDIRS = include lib common daemon $(MAKE_CLINET) $(MAKE_SERVER) \
|
||||
app python tests
|
||||
if WIN32
|
||||
SHELL_EXT = desktop/explorer
|
||||
endif
|
||||
|
||||
SUBDIRS = include data lib common daemon $(MAKE_GUI) $(MAKE_CLINET) $(MAKE_SERVER) \
|
||||
app python tests web
|
||||
|
||||
INTLTOOL = \
|
||||
intltool-extract.in \
|
||||
@ -42,8 +46,8 @@ install-web:
|
||||
endif
|
||||
|
||||
install-data-local:
|
||||
# $(INSTALL) -d $(DESTDIR)$(datarootdir)/applications
|
||||
# $(INSTALL_PROGRAM) debian/seafile.desktop $(DESTDIR)$(datarootdir)/applications/seafile.desktop
|
||||
$(INSTALL) -d $(DESTDIR)$(datarootdir)/applications
|
||||
$(INSTALL_PROGRAM) debian/seafile.desktop $(DESTDIR)$(datarootdir)/applications/seafile.desktop
|
||||
$(INSTALL) -d $(DESTDIR)${bindir}
|
||||
$(INSTALL_PROGRAM) ccnet-web.sh $(DESTDIR)${bindir}
|
||||
$(INSTALL) -d $(DESTDIR)${pkglibdir}
|
||||
|
@ -445,9 +445,18 @@ AC_CONFIG_FILES(
|
||||
ccnet-web.sh
|
||||
Makefile
|
||||
include/Makefile
|
||||
data/Makefile
|
||||
data/icons/Makefile
|
||||
data/icons/16x16/Makefile
|
||||
data/icons/22x22/Makefile
|
||||
data/icons/24x24/Makefile
|
||||
data/icons/32x32/Makefile
|
||||
data/icons/48x48/Makefile
|
||||
lib/Makefile
|
||||
gui/Makefile
|
||||
gui/gtk/Makefile
|
||||
desktop/Makefile
|
||||
desktop/nautilus/Makefile
|
||||
common/Makefile
|
||||
common/cdc/Makefile
|
||||
common/index/Makefile
|
||||
|
1
data/Makefile.am
Normal file
@ -0,0 +1 @@
|
||||
SUBDIRS = icons
|
4
data/icons/16x16/Makefile.am
Normal file
@ -0,0 +1,4 @@
|
||||
icondir = $(datadir)/icons/hicolor/16x16/apps
|
||||
icon_DATA = seafile.png
|
||||
|
||||
EXTRA_DIST = $(icon_DATA)
|
BIN
data/icons/16x16/seafile.png
Normal file
After Width: | Height: | Size: 943 B |
4
data/icons/22x22/Makefile.am
Normal file
@ -0,0 +1,4 @@
|
||||
icondir = $(datadir)/icons/hicolor/22x22/apps
|
||||
icon_DATA = seafile.png
|
||||
|
||||
EXTRA_DIST = $(icon_DATA)
|
BIN
data/icons/22x22/seafile.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
4
data/icons/24x24/Makefile.am
Normal file
@ -0,0 +1,4 @@
|
||||
icondir = $(datadir)/icons/hicolor/24x24/apps
|
||||
icon_DATA = seafile.png
|
||||
|
||||
EXTRA_DIST = $(icon_DATA)
|
BIN
data/icons/24x24/seafile.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
4
data/icons/32x32/Makefile.am
Normal file
@ -0,0 +1,4 @@
|
||||
icondir = $(datadir)/icons/hicolor/32x32/apps
|
||||
icon_DATA = seafile.png
|
||||
|
||||
EXTRA_DIST = $(icon_DATA)
|
BIN
data/icons/32x32/seafile.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
4
data/icons/48x48/Makefile.am
Normal file
@ -0,0 +1,4 @@
|
||||
icondir = $(datadir)/icons/hicolor/48x48/apps
|
||||
icon_DATA = seafile.png
|
||||
|
||||
EXTRA_DIST = $(icon_DATA)
|
BIN
data/icons/48x48/seafile.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
20
data/icons/Makefile.am
Normal file
@ -0,0 +1,20 @@
|
||||
SUBDIRS = 16x16 22x22 24x24 32x32 48x48
|
||||
|
||||
icons = ccnet_daemon_up.png ccnet_daemon_down.png \
|
||||
seafile_transfer_1.png seafile_transfer_2.png \
|
||||
seafile_transfer_3.png seafile_transfer_4.png
|
||||
|
||||
pkgdata_DATA = $(icons)
|
||||
|
||||
EXTRA_DIST = $(icons)
|
||||
|
||||
install-data-hook: update-icon-cache
|
||||
uninstall-hook: update-icon-cache
|
||||
update-icon-cache:
|
||||
@-if test -z "$(DESTDIR)"; then \
|
||||
echo "Updating Gtk icon cache."; \
|
||||
gtk-update-icon-cache || true; \
|
||||
else \
|
||||
echo "*** Icon cache not updated. After (un)install, run this:" \
|
||||
echo "*** gtk-update-icon-cache"; \
|
||||
fi
|
BIN
data/icons/ccnet_daemon_down.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
data/icons/ccnet_daemon_up.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
data/icons/seafile_transfer_1.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
data/icons/seafile_transfer_2.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
data/icons/seafile_transfer_3.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
data/icons/seafile_transfer_4.png
Normal file
After Width: | Height: | Size: 13 KiB |
6
debian/README.Debian
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
Seafile
|
||||
----------------
|
||||
|
||||
For more information about Seafile, please visit http://www.gonggeng.org/seasite
|
||||
|
||||
-- plt <freeplant@gmail.com> Fri, 30 March 2012 16:43:10 +0800
|
5
debian/changelog
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
seafile (0.9.5) unstable; urgency=low
|
||||
|
||||
* Initial release.
|
||||
|
||||
-- plt <freeplant@gmail.com> Fri, 30 March 2012 19:00:32 +0800
|
1
debian/compat
vendored
Normal file
@ -0,0 +1 @@
|
||||
7
|
13
debian/control
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
Source: seafile
|
||||
Section: net
|
||||
Priority: extra
|
||||
Maintainer: Lingtao Pan <freeplant@gmail.com>
|
||||
Build-Depends: debhelper (>= 7), autotools-dev, libssl-dev, libsqlite3-dev, intltool, python-gobject-2-dev, libglib2.0-dev, libevent-dev, libnautilus-extension-dev, libjson-glib-dev, valac, uuid-dev, libgtk2.0-dev, libjson-glib-dev
|
||||
Standards-Version: 3.8.0
|
||||
Homepage: http://www.gonggeng.org/seasite/
|
||||
|
||||
Package: seafile
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}, libglib2.0-0, libgtk2.0-0 (>= 2.16.0), libsqlite3-0, libuuid1, libssl0.9.8 | libssl1.0.0, libevent-2.0-5, python-gobject-2, python-webpy, python-mako, python-markdown, python-simplejson, libnautilus-extension1a,
|
||||
Description: Seafile
|
||||
Seafile is a distributed file synchronization tool.
|
18
debian/copyright
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
This package was debianized by Lingtao Pan <freeplant@gmail.com> on
|
||||
Thu, 15 March 2012 14:45:30 +0800.
|
||||
|
||||
It was downloaded from http://www.gonggeng.org/seasite/download/
|
||||
|
||||
Upstream Author(s):
|
||||
|
||||
Lingtao Pan <freeplant@gmail.com>
|
||||
|
||||
Copyright:
|
||||
|
||||
Copyright (C) 2012 by plt.
|
||||
|
||||
License:
|
||||
This package is not free software.
|
||||
|
||||
The Debian packaging is copyright 2012, Lingtao Pan <freeplant@gmail.com> and
|
||||
is licensed under the GPL, see `/usr/share/common-licenses/GPL'.
|
2
debian/dirs
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
usr/bin
|
||||
usr/sbin
|
0
debian/docs
vendored
Normal file
115
debian/rules
vendored
Executable file
@ -0,0 +1,115 @@
|
||||
#!/usr/bin/make -f
|
||||
# -*- makefile -*-
|
||||
# Sample debian/rules that uses debhelper.
|
||||
# This file was originally written by Joey Hess and Craig Small.
|
||||
# As a special exception, when this file is copied by dh-make into a
|
||||
# dh-make output file, you may use that output file without restriction.
|
||||
# This special exception was added by Craig Small in version 0.37 of dh-make.
|
||||
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
|
||||
# These are used for cross-compiling and for saving the configure script
|
||||
# from having to guess our platform (since we know it already)
|
||||
DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE)
|
||||
DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE)
|
||||
ifneq ($(DEB_HOST_GNU_TYPE),$(DEB_BUILD_GNU_TYPE))
|
||||
CROSS= --build $(DEB_BUILD_GNU_TYPE) --host $(DEB_HOST_GNU_TYPE)
|
||||
else
|
||||
CROSS= --build $(DEB_BUILD_GNU_TYPE)
|
||||
endif
|
||||
|
||||
|
||||
searpc_dir = $(CURDIR)/../deb-libsearpc
|
||||
ccnet_dir = $(CURDIR)/../deb-ccnet
|
||||
shell_ext_dir = $(CURDIR)/desktop/nautilus
|
||||
|
||||
config.status:
|
||||
dh_testdir
|
||||
# Add here commands to configure the package.
|
||||
[ -f configure ] || ./autogen.sh ;
|
||||
./configure $(CROSS) --prefix=/usr --enable-seaf-server=no --mandir=\$${prefix}/share/man --infodir=\$${prefix}/share/info CFLAGS="$(CFLAGS)"
|
||||
|
||||
build: build-stamp
|
||||
|
||||
${searpc_dir}/config.status :
|
||||
cd $(searpc_dir); [ -f configure ] || ./autogen.sh; ./configure $(CROSS) --prefix=/usr --disable-compile-demo --mandir=\$${prefix}/share/man --infodir=\$${prefix}/share/info CFLAGS="$(CFLAGS)"
|
||||
|
||||
${ccnet_dir}/config.status :
|
||||
cd $(ccnet_dir); [ -f configure ] || ./autogen.sh; ./configure $(CROSS) --prefix=/usr --mandir=\$${prefix}/share/man --infodir=\$${prefix}/share/info CFLAGS="$(CFLAGS)"
|
||||
|
||||
build-stamp: config.status ${searpc_dir}/config.status ${ccnet_dir}/config.status
|
||||
dh_testdir
|
||||
|
||||
# Add here commands to compile the package.
|
||||
$(MAKE)
|
||||
#docbook-to-man debian/ccnet.sgml > ccnet.1
|
||||
make -C $(searpc_dir)
|
||||
make -C $(ccnet_dir)
|
||||
make -C $(shell_ext_dir)
|
||||
|
||||
touch $@
|
||||
|
||||
clean:
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
rm -f build-stamp
|
||||
|
||||
# Add here commands to clean up after the build process.
|
||||
[ ! -f Makefile ] || $(MAKE) distclean
|
||||
rm -f config.sub config.guess
|
||||
|
||||
dh_clean
|
||||
|
||||
install: build
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
dh_prep
|
||||
dh_installdirs
|
||||
|
||||
# Add here commands to install the package into debian/ccnet.
|
||||
$(MAKE) DESTDIR=$(CURDIR)/debian/seafile install
|
||||
make -C $(searpc_dir) DESTDIR=$(CURDIR)/debian/seafile install
|
||||
make -C $(ccnet_dir) DESTDIR=$(CURDIR)/debian/seafile install
|
||||
make -C $(shell_ext_dir) DESTDIR=$(CURDIR)/debian/seafile install
|
||||
rm -rf $(CURDIR)/debian/seafile/usr/include
|
||||
|
||||
|
||||
# Build architecture-independent files here.
|
||||
binary-indep: install
|
||||
# We have nothing to do by default.
|
||||
|
||||
# Build architecture-dependent files here.
|
||||
binary-arch: install
|
||||
dh_testdir
|
||||
dh_testroot
|
||||
dh_installchangelogs
|
||||
dh_installdocs
|
||||
# dh_installexamples
|
||||
# dh_install
|
||||
# dh_installmenu
|
||||
# dh_installdebconf
|
||||
# dh_installlogrotate
|
||||
# dh_installemacsen
|
||||
# dh_installpam
|
||||
# dh_installmime
|
||||
# dh_python
|
||||
# dh_installinit
|
||||
# dh_installcron
|
||||
# dh_installinfo
|
||||
# dh_installman
|
||||
# dh_link
|
||||
dh_strip
|
||||
dh_compress
|
||||
dh_fixperms
|
||||
# dh_perl
|
||||
# dh_makeshlibs
|
||||
dh_installdeb
|
||||
dh_shlibdeps
|
||||
dh_gencontrol
|
||||
dh_md5sums
|
||||
dh_builddeb
|
||||
|
||||
binary: binary-indep binary-arch
|
||||
.PHONY: build clean binary-indep binary-arch binary install
|
8
debian/seafile.desktop
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
[Desktop Entry]
|
||||
Name=Seafile
|
||||
Comment=Seafile
|
||||
TryExec=seafile-applet
|
||||
Exec=seafile-applet
|
||||
Icon=seafile
|
||||
Type=Application
|
||||
Categories=Network:FileTransfer;
|
5
desktop/Makefile.am
Normal file
@ -0,0 +1,5 @@
|
||||
if WIN32
|
||||
SUBDIRS = explorer
|
||||
else
|
||||
SUBDIRS = nautilus
|
||||
endif
|
132
desktop/README
Normal file
@ -0,0 +1,132 @@
|
||||
#+-*-mode:org -*-
|
||||
#+startup: showall
|
||||
|
||||
#+title: Explorer/Nautilus 扩展的编译、注册和使用
|
||||
|
||||
|
||||
* ${topsrcdir}/desktop 代码结构
|
||||
|
||||
|--------------+---------------------------------------------|
|
||||
| folder | contents |
|
||||
|--------------+---------------------------------------------|
|
||||
| common | common parts used in both Explorer/Nautilus |
|
||||
| common/icons | icons for menu items |
|
||||
| explorer | win32 explorer specific part |
|
||||
| nautilus | nautilus specific part |
|
||||
|
||||
|
||||
* Explorer
|
||||
|
||||
** 一些说明
|
||||
|
||||
1) shell extension需要调用seafile.exe来获取信息
|
||||
|
||||
2) 但我们不能把seafile.exe所在的目录加入到系统的path中去。因为打包后,
|
||||
seafile.exe和glib等在同一目录,如果加入path有可能会影响用户的其他
|
||||
软件
|
||||
|
||||
3) 因此采用的方法是在注册shell extension的dll时,把seafile.exe
|
||||
的路径写到注册表中,运行时从注册表中读取这一路径。
|
||||
|
||||
** 目前实现的功能
|
||||
|
||||
*** 右键菜单:
|
||||
|
||||
+ 如果检测到daemon没有运行,则在菜单中提供启动daemon的选项
|
||||
|
||||
+ 检测当前路径是否是在repo中,如果不是则提供"从此处创建repo"的选项,用户点击
|
||||
后自动打开web端的"创建文件盒"页面
|
||||
|
||||
+ 在web中打开该repo
|
||||
|
||||
+ 在auto-commit/manual-mode 之间切换的菜单项
|
||||
|
||||
*** repo目录的特殊icon
|
||||
|
||||
如果某个文件夹是某个repo的顶层目录,那么就在文件夹的图标上面额外显示
|
||||
一个特殊的图标。
|
||||
|
||||
|
||||
** 不打包编译、使用(开发时使用)
|
||||
|
||||
1) 修改 ${topsrcdir}/desktop/explorer/seaf-dll.h 中的宏定义
|
||||
|
||||
#+begin_src c
|
||||
#define SEAF_MINGW_PATH \
|
||||
"c:\\MinGW\\msys\\1.0\\bin;" \
|
||||
"c:\\MinGW\\msys\\1.0\\lib;" \
|
||||
"c:\\MinGW\\msys\\1.0\\local\\bin;" \
|
||||
"c:\\MinGW\\msys\\1.0\\local\\lib;" \
|
||||
"c:\\MinGW\\lib;c:\\MinGW\\bin;" \
|
||||
"c:\\Python26;" \
|
||||
"c:\\Python26\\Scripts;" ;
|
||||
#+end_src c
|
||||
|
||||
把上面的路径改成你相应的路径
|
||||
|
||||
2) cd ${topsrcdir}/desktop/explorer
|
||||
3) make debug
|
||||
4) make install-sys
|
||||
|
||||
因为打包之后所有的 exe/lib 都在同一目录下,因此建议先打包之后再注册、测试。
|
||||
|
||||
** 打包
|
||||
|
||||
1) cd ${topsrcdir}/desktop/explorer
|
||||
2) make
|
||||
|
||||
3) 用setupwin.sh打包,比如打包到默认的 /d/workspace/ccnet-green/
|
||||
|
||||
4) cd /d/workspace/ccnet-green/
|
||||
5) ./register-system-wide.bat
|
||||
|
||||
|
||||
** 其他
|
||||
|
||||
1) 关于shell extension 的实现及相关链接,见 ${topsrcdir}/desktop/explorer/README.org
|
||||
|
||||
2) 由于shell extension 的dll由explorer.exe调用,如果要使用glib,就需
|
||||
要把glib所在的目录加入到系统的PATH变量中去,而这可能会影响用户其他
|
||||
的程序,因此没有使用glib。
|
||||
|
||||
3) 如果重新编译了seaf_shell_ext.dll, 必须从进程管理器杀掉explorer/iexplorer进程
|
||||
才能覆盖老的dll,否则cp/rm 等操作会报告 "permission denied"
|
||||
|
||||
|
||||
* Nautilus
|
||||
|
||||
Nautilus下目前只实现了 MenuProvider 扩展。
|
||||
|
||||
** 配置
|
||||
|
||||
Debian/Ubuntu下 需要先安装 nautilus-extension-dev 软件包。
|
||||
#+begin_src sh
|
||||
sudo aptitude install nautilus-extension-dev
|
||||
#+end_src
|
||||
|
||||
** 编译安装
|
||||
|
||||
1) 先从Makefile.in 生成 Makefile
|
||||
#+begin_src sh
|
||||
cd ${topsrcdir}
|
||||
./config.status
|
||||
#+end_src
|
||||
|
||||
2) 编译安装
|
||||
#+begin_src sh
|
||||
cd ${topsrcdir}/desktop/nautilus/ && make && make install
|
||||
#+end_src
|
||||
|
||||
** 注册
|
||||
|
||||
`make install' 把扩展的.so文件安装到 /usr/lib/nautilus/extensions-2.0/ 目录
|
||||
下之后,Nautils 启动时会自行调用扩展,无需另外注册。
|
||||
|
||||
|
||||
** 使用
|
||||
|
||||
需要重启nautilus以使扩展生效。
|
||||
|
||||
#+begin_src sh
|
||||
pkill nautilus
|
||||
#+end_src
|
11
desktop/common/gen-seaf-lang-gbk.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Edit the utf8 version of seaf-lang.h, and use this script to convert it to
|
||||
# gbk when done.
|
||||
|
||||
SCRIPT=$(readlink -f "$0")
|
||||
CURDIR=$(dirname "${SCRIPT}")
|
||||
SRC=${CURDIR}/seaf-lang.h
|
||||
DEST=${CURDIR}/seaf-lang-gbk.h
|
||||
|
||||
iconv -t GBK -f UTF-8 "${SRC}" > "${DEST}"
|
BIN
desktop/common/icons/seaf_ext.ico
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
desktop/common/icons/seaf_ext_create.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
desktop/common/icons/seaf_ext_refresh.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
desktop/common/icons/seaf_ext_start.ico
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
desktop/common/icons/seaf_ext_web_fx.ico
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
desktop/common/icons/seaf_ext_web_ie.ico
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
desktop/common/icons/seaf_repo.ico
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
desktop/common/icons/seafdir.ico
Normal file
After Width: | Height: | Size: 17 KiB |
351
desktop/common/menu-engine.c
Normal file
@ -0,0 +1,351 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include "strbuf.h"
|
||||
#include "seaf-utils.h"
|
||||
#include "seaf-dlgs.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#include "../explorer/seaf-menu.h"
|
||||
#else
|
||||
#include "../nautilus/seaf-menu.h"
|
||||
#endif
|
||||
|
||||
#include "seaf-ext-log.h"
|
||||
|
||||
#include "menu-engine.h"
|
||||
|
||||
|
||||
#ifndef WIN32
|
||||
|
||||
extern void send_refresh_menu_signal();
|
||||
#define UPDATE_MENU \
|
||||
do { \
|
||||
send_refresh_menu_signal(seaf_menu->menu_provider); \
|
||||
} while (0)
|
||||
|
||||
#else
|
||||
|
||||
#define UPDATE_MENU
|
||||
extern void append_active_menu(SeafMenu *seaf_menu, const struct menu_item *item);
|
||||
|
||||
#endif
|
||||
|
||||
extern void reset_active_menu(SeafMenu *seaf_menu);
|
||||
|
||||
/**
|
||||
We use the following struct to contain all possible menu items.
|
||||
|
||||
When query_context_menu is called, we first build a mask, i.e. the
|
||||
'selection' variable, according to the file/folder current being clicked
|
||||
on, and build a customized menu by matching the selection mask with every
|
||||
menu item's flags field.
|
||||
|
||||
For win32:
|
||||
|
||||
At the same time, every selected menu item is added to the active_menu[]
|
||||
list, so later when the Shell calls "get_command_string" or
|
||||
"invoke_command", we can provide what he wants by referring to this list.
|
||||
|
||||
Strings are all kept in `seaf-lang.h'.
|
||||
**/
|
||||
|
||||
static struct menu_item seafile_menu[] = {
|
||||
{ SEAF_MI_NODAEMON,
|
||||
SEAF_RUN_APPLET,
|
||||
MENU_STRING_START_SEAFILE,
|
||||
MENU_HELPTEXT_START_SEAFILE,
|
||||
SEAF_EXT_UI_DIR "seaf_ext_start" ICON_EXT},
|
||||
|
||||
/*
|
||||
{ SEAF_MI_DAEMON,
|
||||
SEAF_REFRESH_CACHE,
|
||||
MENU_STRING_REFRESH,
|
||||
MENU_HELPTEXT_REFRESH,
|
||||
SEAF_EXT_UI_DIR "seaf_ext_refresh" ICON_EXT},
|
||||
*/
|
||||
|
||||
/* { SEAF_MI_DAEMON | SEAF_MI_NOREPO, */
|
||||
/* SEAF_INIT, */
|
||||
/* MENU_STRING_INIT_REPO, */
|
||||
/* MENU_HELPTEXT_INIT_REPO, */
|
||||
/* SEAF_EXT_UI_DIR "seaf_ext_create" ICON_EXT}, */
|
||||
|
||||
{ SEAF_MI_DAEMON | SEAF_MI_REPO,
|
||||
SEAF_OPEN_WEB,
|
||||
MENU_STRING_OPEN_WEB,
|
||||
MENU_HELPTEXT_OPEN_WEB,
|
||||
#ifdef WIN32
|
||||
SEAF_EXT_UI_DIR "seaf_ext_web_ie" ICON_EXT,
|
||||
#else
|
||||
SEAF_EXT_UI_DIR "seaf_ext_web_fx" ICON_EXT,
|
||||
#endif
|
||||
},
|
||||
|
||||
#ifdef WIN32
|
||||
{ SEAF_MI_DAEMON | SEAF_MI_REPO, SEAF_NONE, NULL, NULL, NULL},
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#define _MI_AUTO 0
|
||||
#define _MI_MANUAL 0
|
||||
#else
|
||||
#define _MI_AUTO SEAF_MI_TURN_ON_AUTO
|
||||
#define _MI_MANUAL SEAF_MI_TURN_OFF_AUTO
|
||||
#endif
|
||||
|
||||
{ SEAF_MI_DAEMON | SEAF_MI_REPO | _MI_AUTO,
|
||||
SEAF_TURN_ON_AUTO,
|
||||
MENU_STRING_AUTO,
|
||||
MENU_HELPTEXT_AUTO,
|
||||
SEAF_EXT_UI_DIR "seaf_ext_auto" ICON_EXT},
|
||||
|
||||
{ SEAF_MI_DAEMON | SEAF_MI_REPO | _MI_MANUAL,
|
||||
SEAF_TURN_OFF_AUTO,
|
||||
MENU_STRING_MANUAL,
|
||||
MENU_HELPTEXT_MANUAL,
|
||||
SEAF_EXT_UI_DIR "seaf_ext_manual" ICON_EXT},
|
||||
|
||||
};
|
||||
|
||||
char *seaf_ext_ico;
|
||||
char *seaf_repo_ico;
|
||||
|
||||
void translate_icon_paths()
|
||||
{
|
||||
static bool done = FALSE;
|
||||
if (done)
|
||||
return;
|
||||
|
||||
const char *folder;
|
||||
|
||||
char *icon_subdir;
|
||||
|
||||
#ifdef WIN32
|
||||
folder = get_this_dll_folder();
|
||||
icon_subdir = "icons/";
|
||||
#else
|
||||
folder = SEAF_EXT_UI_DIR;
|
||||
icon_subdir = "";
|
||||
#endif
|
||||
|
||||
#define DO_TRANSLATE(icon, name) \
|
||||
do { \
|
||||
struct strbuf sb = STRBUF_INIT; \
|
||||
strbuf_addstr(&sb, folder); \
|
||||
strbuf_addstr(&sb, icon_subdir); \
|
||||
strbuf_addstr(&sb, name); \
|
||||
icon = strbuf_detach(&sb, NULL); \
|
||||
} while (0)
|
||||
|
||||
int n = sizeof(seafile_menu) / sizeof(struct menu_item);
|
||||
for (n--; n >= 0; n--) {
|
||||
char *icon = seafile_menu[n].icon;
|
||||
if (icon) {
|
||||
DO_TRANSLATE (seafile_menu[n].icon, icon);
|
||||
}
|
||||
}
|
||||
|
||||
DO_TRANSLATE (seaf_ext_ico, "seaf_ext" ICON_EXT);
|
||||
DO_TRANSLATE (seaf_repo_ico, "seaf_repo" ICON_EXT);
|
||||
|
||||
done = TRUE;
|
||||
seaf_ext_log ("icon path caculated");
|
||||
}
|
||||
|
||||
static inline void
|
||||
start_thread(SeafThreadFunc func, void *data)
|
||||
{
|
||||
seaf_ext_start_thread(func, data, NULL);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
ccnet_applet_is_running ()
|
||||
{
|
||||
return process_is_running ("seafile-applet");
|
||||
}
|
||||
|
||||
static int
|
||||
do_open_browser (SeafMenu *seaf_menu)
|
||||
{
|
||||
char *repo_id = seaf_menu->repo_id;
|
||||
char *url = NULL;
|
||||
char *s = SEAF_HTTP_ADDR "/repo/?repo=";
|
||||
|
||||
url = do_str_add (s, repo_id);
|
||||
open_browser(url);
|
||||
free(url);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
start_ccnet_applet (SeafMenu *seaf_menu)
|
||||
{
|
||||
if (ccnet_applet_is_running())
|
||||
return 0;
|
||||
|
||||
if (spawn_process("seafile-applet", NULL) < 0) {
|
||||
msgbox_warning (MSG_FAIL_TO_START_SEAFILE);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
set_repo_auto (SeafMenu *seaf_menu, bool on)
|
||||
{
|
||||
char *repo_id = seaf_menu->repo_id;
|
||||
char *cmd = on ? "set-auto" : "set-manual";
|
||||
|
||||
char request[128];
|
||||
snprintf (request, sizeof(request), "%s\t%s", cmd, repo_id);
|
||||
|
||||
int status = send_ext_pipe_request(request);
|
||||
if (status < 0) {
|
||||
msgbox_warning (MSG_OPERATION_FAILED);
|
||||
}
|
||||
|
||||
UPDATE_MENU;
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
static int set_repo_auto_on(SeafMenu *seaf_menu)
|
||||
{
|
||||
return set_repo_auto(seaf_menu, TRUE);
|
||||
}
|
||||
|
||||
|
||||
static int set_repo_auto_off(SeafMenu *seaf_menu)
|
||||
{
|
||||
return set_repo_auto(seaf_menu, FALSE);
|
||||
}
|
||||
|
||||
|
||||
/* Main command handler */
|
||||
void
|
||||
dispatch_menu_command (void *arg1, void *arg2)
|
||||
{
|
||||
SeafMenu *seaf_menu = NULL;
|
||||
seafile_op op = *(seafile_op *)arg2;
|
||||
|
||||
#ifdef WIN32
|
||||
seaf_menu = arg1;
|
||||
#else
|
||||
NautilusMenuItem *item = arg1;
|
||||
seaf_menu = g_object_get_data((GObject *)item, "seaf_menu");
|
||||
#endif
|
||||
|
||||
if (!seaf_menu)
|
||||
return;
|
||||
/* No daemon detected */
|
||||
if (op == SEAF_RUN_APPLET) {
|
||||
start_thread((SeafThreadFunc)start_ccnet_applet, seaf_menu);
|
||||
|
||||
} else {
|
||||
switch (op) {
|
||||
case SEAF_OPEN_WEB:
|
||||
start_thread((SeafThreadFunc)do_open_browser, seaf_menu);
|
||||
break;
|
||||
|
||||
case SEAF_TURN_OFF_AUTO:
|
||||
start_thread((SeafThreadFunc)set_repo_auto_off, seaf_menu);
|
||||
break;
|
||||
|
||||
case SEAF_TURN_ON_AUTO:
|
||||
start_thread((SeafThreadFunc)set_repo_auto_on, seaf_menu);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
repo_is_auto (const char *repo_id)
|
||||
{
|
||||
char request[128];
|
||||
bool result = FALSE;
|
||||
snprintf (request, sizeof(request), "%s\t%s", "query-auto", repo_id);
|
||||
char *response = get_ext_pipe_response(request);
|
||||
if (response && strcmp(response, "true") == 0) {
|
||||
result = TRUE;
|
||||
} else {
|
||||
result = FALSE;
|
||||
}
|
||||
|
||||
if (response) {
|
||||
free (response);
|
||||
}
|
||||
else
|
||||
seaf_ext_log ("query-auto returned NULL!");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
build_menu_mask(SeafMenu *seaf_menu)
|
||||
{
|
||||
/* Don't show seafile menu when seafile-applet is not running */
|
||||
if (!ext_pipe_is_connected() && connect_ext_pipe() < 0) {
|
||||
/* seaf_menu->selection = SEAF_MI_ALWAYS | SEAF_MI_NODAEMON; */
|
||||
return;
|
||||
}
|
||||
|
||||
seaf_menu->selection |= SEAF_MI_DAEMON;
|
||||
get_repo_id_wt (seaf_menu);
|
||||
if (!ext_pipe_is_connected())
|
||||
goto out;
|
||||
if (seaf_menu->repo_id[0] == '\0') {
|
||||
/* Not in a seaf repo */
|
||||
seaf_menu->selection |= SEAF_MI_NOREPO;
|
||||
|
||||
} else {
|
||||
seaf_menu->selection |= SEAF_MI_REPO;
|
||||
if (repo_is_auto(seaf_menu->repo_id)) {
|
||||
seaf_menu->selection |= SEAF_MI_TURN_OFF_AUTO;
|
||||
} else {
|
||||
seaf_menu->selection |= SEAF_MI_TURN_ON_AUTO;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
if (!ext_pipe_is_connected())
|
||||
seaf_menu->selection = SEAF_MI_ALWAYS | SEAF_MI_NODAEMON;
|
||||
}
|
||||
|
||||
static void
|
||||
build_menu_by_mask (SeafMenu *seaf_menu)
|
||||
{
|
||||
int i;
|
||||
int n = sizeof(seafile_menu) / sizeof(struct menu_item);
|
||||
|
||||
/* Build menu according to 'selection' mask */
|
||||
for (i = 0; i < n; i++) {
|
||||
unsigned int flags = seafile_menu[i].flags;
|
||||
if ((flags & seaf_menu->selection) == flags) {
|
||||
if (!build_menu_item (seaf_menu, &seafile_menu[i]))
|
||||
break;
|
||||
#ifdef WIN32
|
||||
append_active_menu(seaf_menu, &seafile_menu[i]);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The main function in this module. */
|
||||
int build_seafile_menu(SeafMenu *seaf_menu)
|
||||
{
|
||||
reset_active_menu(seaf_menu);
|
||||
build_menu_mask (seaf_menu);
|
||||
build_menu_by_mask (seaf_menu);
|
||||
|
||||
return 0;
|
||||
}
|
48
desktop/common/menu-engine.h
Normal file
@ -0,0 +1,48 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
#ifndef MENU_ENGINE_H
|
||||
#define MENU_ENGINE_H
|
||||
|
||||
/* Menu item flags */
|
||||
|
||||
#define SEAF_MI_ALWAYS (1 << 0)
|
||||
#define SEAF_MI_NOREPO (1 << 1) /* not in a seaf repo */
|
||||
#define SEAF_MI_REPO (1 << 2)
|
||||
#define SEAF_MI_WORKTREE_CHANGED (1 << 3)
|
||||
#define SEAF_MI_WORKTREE_NOT_CHANGED (1 << 4)
|
||||
#define SEAF_MI_TURN_ON_AUTO (1 << 5)
|
||||
#define SEAF_MI_TURN_OFF_AUTO (1 << 6)
|
||||
#define SEAF_MI_REPO_STATUS (1 << 7)
|
||||
|
||||
#define SEAF_MI_NODAEMON (1 << 30)
|
||||
#define SEAF_MI_DAEMON (1 << 31) /* indicates need daemon or not */
|
||||
|
||||
typedef enum {
|
||||
SEAF_NONE = 0, /* for menu separator */
|
||||
SEAF_RUN_APPLET,
|
||||
SEAF_REFRESH_CACHE,
|
||||
SEAF_OPEN_WEB,
|
||||
SEAF_TURN_ON_AUTO,
|
||||
SEAF_TURN_OFF_AUTO,
|
||||
SEAF_OP_MAX,
|
||||
} seafile_op;
|
||||
|
||||
struct menu_item {
|
||||
unsigned int flags;
|
||||
seafile_op op;
|
||||
char *string; /* displayed in the menu */
|
||||
char *helptext; /* displayed in the status bar */
|
||||
char *icon; /* icon file path */
|
||||
};
|
||||
|
||||
struct SeafMenu;
|
||||
/* called in query_context_menu, build a customized menu according to
|
||||
* the file current operated on.
|
||||
*/
|
||||
int build_seafile_menu(struct SeafMenu *data);
|
||||
|
||||
void dispatch_menu_command (void *arg1, void *arg2);
|
||||
|
||||
/* Translate path to menu item icon according to the user's installation path */
|
||||
void translate_icon_paths();
|
||||
|
||||
#endif /* MENU_ENGINE_H */
|
154
desktop/common/platform.h
Normal file
@ -0,0 +1,154 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
#ifndef SEAF_PLATFORM_H
|
||||
#define SEAF_PLATFORM_H
|
||||
|
||||
#ifndef WIN32
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
/* platform dependent stuff */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define SEAF_HTTP_ADDR "http://127.0.0.1:13420"
|
||||
|
||||
/* WIN32 is defined on both mingw-32 and mingw-64;
|
||||
* WIN64 is only defined on mingw-64
|
||||
*/
|
||||
#ifdef WIN32
|
||||
|
||||
#define sleep(x) Sleep(x * 1000)
|
||||
#include <windows.h>
|
||||
#include <shlwapi.h>
|
||||
#include <shlobj.h>
|
||||
#include "seaf-lang-gbk.h"
|
||||
|
||||
#define str_case_str StrStrI
|
||||
#define bool BOOL
|
||||
#define SEAF_EXT_UI_DIR
|
||||
#define ICON_EXT ".ico"
|
||||
|
||||
#ifndef WIN64
|
||||
/* this flag does not make sense on non-64 bit windows */
|
||||
#define KEY_WOW64_64KEY 0
|
||||
#endif
|
||||
|
||||
#else /* LINUX */
|
||||
|
||||
#include <unistd.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <libnautilus-extension/nautilus-menu-provider.h>
|
||||
#include "seaf-lang.h"
|
||||
|
||||
#define bool gboolean
|
||||
#define MAX_PATH PATH_MAX
|
||||
#define str_case_str strcasestr
|
||||
#define ICON_EXT ".ico"
|
||||
|
||||
#endif
|
||||
|
||||
const char *get_home_dir();
|
||||
|
||||
/* escape a path to uri */
|
||||
char *seaf_uri_escape(const char *path);
|
||||
|
||||
/* test whether a process with a given name is running */
|
||||
bool process_is_running (const char *process_name);
|
||||
|
||||
/* Convert between utf-8 and current os locale. The returned string
|
||||
* should be freed after use.
|
||||
*/
|
||||
inline char *locale_to_utf8 (const char *src);
|
||||
inline char *locale_from_utf8 (const char *src);
|
||||
|
||||
/* If `path' is a normal file, return the path of the folder containing
|
||||
* it(with the trailing '/'); If `path' is a folder, return strdup(path)
|
||||
*/
|
||||
char *get_folder_path (const char *path);
|
||||
|
||||
/* Get the base name of a path. */
|
||||
char *get_base_name (const char *path);
|
||||
|
||||
struct menu_item;
|
||||
struct SeafMenu;
|
||||
|
||||
/* Insert a new menu item according to its type */
|
||||
bool build_menu_item(struct SeafMenu *menu, const struct menu_item *mi);
|
||||
|
||||
/* Open the url in default web browser */
|
||||
void open_browser(char *url);
|
||||
|
||||
bool seaf_mutex_init (void *p_mutex);
|
||||
|
||||
/* Try to accuqire the `mutex', blocking if `blocking' param is TRUE.
|
||||
* When `blocking' is TRUE, this funciton won't return until the mutex
|
||||
* is accquired. When `blocking' is FALSE, return immediately with the
|
||||
* return value indicating whether the mutex is accquired or not.
|
||||
*/
|
||||
inline bool seaf_mutex_acquire(void *vmutex, bool blocking);
|
||||
|
||||
/* Release a lock held by me. */
|
||||
inline bool seaf_mutex_release(void *vmutex);
|
||||
|
||||
/* use threads to handle menu commands asynchronously */
|
||||
typedef int (*SeafThreadFunc)(void *);
|
||||
|
||||
typedef struct seaf_ext_job {
|
||||
SeafThreadFunc thread_func;
|
||||
void *data;
|
||||
} SeafExtJob;
|
||||
|
||||
/* Start a new thread to run a menu command; If p_handle is not null, then its
|
||||
* value is set to the handle of the created thread; Otherwise the new thread
|
||||
* handle is closed(in windows).
|
||||
*/
|
||||
int seaf_ext_start_thread(SeafThreadFunc thread_func, void *data, void *p_handle);
|
||||
|
||||
/* Note: Acquire the pipe_mutex before call any of these three functions, or
|
||||
* r/w the value of `ext_pipe_connected' */
|
||||
int send_ext_pipe_request_wrapper (const char *request);
|
||||
|
||||
char *read_ext_pipe_response ();
|
||||
|
||||
int connect_ext_pipe ();
|
||||
|
||||
inline bool ext_pipe_is_connected ();
|
||||
|
||||
int spawn_process (char *cmdline_in, char *working_directory);
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
/**
|
||||
* ---------------------------------
|
||||
* WIN32 Specific functions
|
||||
* ---------------------------------
|
||||
*/
|
||||
|
||||
/* Get the path to this dll */
|
||||
const char *get_this_dll_filename();
|
||||
/* Get the folder containing this dll */
|
||||
const char *get_this_dll_folder();
|
||||
|
||||
/* Convert from wchar_t string to multi-byte char string */
|
||||
char *wchar_to_char (const wchar_t *src);
|
||||
wchar_t *char_to_wchar (const char *src);
|
||||
|
||||
int kill_process (const char *process_name);
|
||||
|
||||
int seaf_ext_pipe_prepare();
|
||||
|
||||
#else
|
||||
|
||||
/* Linux specific functions */
|
||||
|
||||
bool is_main_thread();
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* SEAF_PLATFORM_H */
|
||||
|
28
desktop/common/seaf-dlgs.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef SEAF_DLGS_H
|
||||
#define SEAF_DLGS_H
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#ifdef WIN32
|
||||
typedef enum
|
||||
{
|
||||
GTK_MESSAGE_INFO,
|
||||
GTK_MESSAGE_WARNING,
|
||||
GTK_MESSAGE_QUESTION,
|
||||
GTK_MESSAGE_ERROR,
|
||||
GTK_MESSAGE_OTHER
|
||||
} GtkMessageType;
|
||||
#endif
|
||||
|
||||
void msgbox_full(char *msg_in, GtkMessageType type, void *user_data);
|
||||
|
||||
#define msgbox(msg) msgbox_full((msg), GTK_MESSAGE_INFO, NULL)
|
||||
|
||||
#define msgbox_warning(msg) msgbox_full((msg), GTK_MESSAGE_WARNING, NULL)
|
||||
|
||||
bool msgbox_yes_or_no(char *question_in, void *user_data);
|
||||
|
||||
void prompt_create_repo_dlg (const char *worktree);
|
||||
|
||||
|
||||
#endif /* SEAF_DLGS_H */
|
91
desktop/common/seaf-ext-log.c
Normal file
@ -0,0 +1,91 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#include <time.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "seaf-ext-log.h"
|
||||
#include "seaf-utils.h"
|
||||
#include "strbuf.h"
|
||||
|
||||
|
||||
static FILE *log_fp;
|
||||
|
||||
static char *
|
||||
get_log_path()
|
||||
{
|
||||
const char *home = get_home_dir();
|
||||
if (!home)
|
||||
return NULL;
|
||||
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
strbuf_addf (&sb, "%s/seafile_extension.log", home);
|
||||
|
||||
return strbuf_detach(&sb, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
seaf_ext_log_start ()
|
||||
{
|
||||
if (log_fp)
|
||||
return;
|
||||
|
||||
static char *log_path;
|
||||
if (!log_path)
|
||||
log_path = get_log_path();
|
||||
|
||||
if (log_path)
|
||||
log_fp = fopen (log_path, "a");
|
||||
|
||||
if (log_fp) {
|
||||
seaf_ext_log ("\n----------------------------------\n"
|
||||
"log file initialized, %s"
|
||||
"\n----------------------------------\n"
|
||||
, log_path);
|
||||
} else {
|
||||
fprintf (stderr, "[LOG] Can't init log file, %s\n", log_path);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
seaf_ext_log_stop ()
|
||||
{
|
||||
if (log_fp) {
|
||||
fclose (log_fp);
|
||||
log_fp = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
inline void
|
||||
seaf_ext_log_aux (char *format, ... )
|
||||
{
|
||||
if (!log_fp)
|
||||
seaf_ext_log_start();
|
||||
|
||||
if (log_fp) {
|
||||
va_list params;
|
||||
char buffer[1024];
|
||||
int length = 0;
|
||||
|
||||
va_start(params, format);
|
||||
length = vsnprintf(buffer, sizeof(buffer), format, params);
|
||||
va_end(params);
|
||||
|
||||
/* Write the timestamp. */
|
||||
time_t t;
|
||||
struct tm *tm;
|
||||
char buf[256];
|
||||
|
||||
t = time(NULL);
|
||||
tm = localtime(&t);
|
||||
strftime (buf, 256, "[%y/%m/%d %H:%M:%S] ", tm);
|
||||
|
||||
fputs (buf, log_fp);
|
||||
if (fwrite(buffer, sizeof(char), length, log_fp) < length)
|
||||
return;
|
||||
|
||||
fputc('\n', log_fp);
|
||||
fflush(log_fp);
|
||||
}
|
||||
}
|
11
desktop/common/seaf-ext-log.h
Normal file
@ -0,0 +1,11 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
|
||||
void seaf_ext_log_start ();
|
||||
void seaf_ext_log_stop ();
|
||||
|
||||
inline void seaf_ext_log_aux (char *msg, ... );
|
||||
|
||||
#define seaf_ext_log(format, ... ) \
|
||||
seaf_ext_log_aux("%s(line %d) %s: " format, \
|
||||
__FILE__, __LINE__, __func__, ##__VA_ARGS__) \
|
||||
|
95
desktop/common/seaf-lang.h
Normal file
@ -0,0 +1,95 @@
|
||||
#ifndef SEAF_LANG_EN
|
||||
|
||||
/* --------------------------------------------
|
||||
* Chinese menu text
|
||||
* -------------------------------------------- */
|
||||
|
||||
#define MENU_STRING_START_SEAFILE "启动 Seafile"
|
||||
#define MENU_HELPTEXT_START_SEAFILE "Seafile 尚未运行,点击以启动。"
|
||||
|
||||
#define MENU_STRING_REFRESH "刷新"
|
||||
#define MENU_HELPTEXT_REFRESH "对同步目录进行了创建/删除/同步等操作之后,需要刷新 Seafile 缓存"
|
||||
|
||||
#define MENU_STRING_INIT_REPO "变为同步目录"
|
||||
#define MENU_HELPTEXT_INIT_REPO "把当前目录变为一个同步目录"
|
||||
|
||||
#define MENU_STRING_OPEN_WEB "打开管理页面"
|
||||
#define MENU_HELPTEXT_OPEN_WEB "在浏览器中打开当前同步目录的页面"
|
||||
|
||||
#ifdef WIN32
|
||||
#define MENU_STRING_AUTO "打开自动同步"
|
||||
#define MENU_STRING_MANUAL "关闭自动同步"
|
||||
#else
|
||||
#define MENU_STRING_AUTO "打开自动同步"
|
||||
#define MENU_STRING_MANUAL "关闭自动同步"
|
||||
#endif
|
||||
|
||||
#define MENU_HELPTEXT_AUTO "打开自动同步后,目录有更改时会自动被同步,无需用户自己动手"
|
||||
#define MENU_HELPTEXT_MANUAL "关闭自动同步后,当前目录的更改由需要由用户自己点击同步按钮来同步"
|
||||
|
||||
/* --------------------------------------------
|
||||
* Chinese messages
|
||||
* -------------------------------------------- */
|
||||
|
||||
#define MSG_FAIL_TO_START_SEAFILE "启动 Seafile 失败"
|
||||
#define MSG_BROWSER_NOT_FOUND "没有找到你的浏览器"
|
||||
#define MSG_OPEN_URL_YOURSELF "请尝试在浏览器中手工打开这个链接"
|
||||
#define MSG_OPERATION_FAILED "操作失败"
|
||||
|
||||
#define MSG_PASSWD_EMPTY "密码不能为空"
|
||||
#define MSG_PASSWD_TOO_SHORT "密码太短"
|
||||
#define MSG_PASSWD_TOO_LONG "密码太长"
|
||||
#define MSG_PASSWD_MISMATCH "两次输入的密码不一致"
|
||||
|
||||
#define MSG_DESC_EMPTY "描述不能为空"
|
||||
#define MSG_DESC_TOO_SHORT "描述太短"
|
||||
#define MSG_DESC_TOO_LONG "描述太长"
|
||||
#define MSG_ENSURE_QUIT "确认退出?"
|
||||
|
||||
#define MSG_CREATING_REPO "正在创建同步目录"
|
||||
#define MSG_ERROR_NO_DAEMON "Seafile 未启动"
|
||||
#define MSG_CREATE_REPO_SUCCESS "操作成功"
|
||||
#define MSG_CREATE_REPO_FAILED "操作失败"
|
||||
|
||||
#define MSG_INTERNAL_ERROR "内部错误"
|
||||
|
||||
|
||||
#else
|
||||
|
||||
/* --------------------------------------------
|
||||
* English menu text
|
||||
* -------------------------------------------- */
|
||||
|
||||
#define MENU_STRING_START_SEAFILE "Start Seafile"
|
||||
#define MENU_HELPTEXT_START_SEAFILE "Seafile is not running. Click to start it."
|
||||
|
||||
#define MENU_STRING_REFRESH "Refresh"
|
||||
#define MENU_HELPTEXT_REFRESH "Refresh the cached data after operations such as create/delete/commit"
|
||||
|
||||
#define MENU_STRING_INIT_REPO "Init a repo here"
|
||||
#define MENU_HELPTEXT_INIT_REPO "create a repository from current folder "
|
||||
|
||||
#define MENU_STRING_OPEN_WEB "View in browser"
|
||||
#define MENU_HELPTEXT_OPEN_WEB "view the status of this repo in your web browser"
|
||||
|
||||
#ifdef WIN32
|
||||
#define MENU_STRING_AUTO "Auto commit"
|
||||
#define MENU_STRING_MANUAL "Manual mode"
|
||||
#else
|
||||
#define MENU_STRING_AUTO "Switch to auto commit"
|
||||
#define MENU_STRING_MANUAL "Switch to manual mode"
|
||||
#endif
|
||||
|
||||
#define MENU_HELPTEXT_AUTO "Changes of this repository is committed automatically"
|
||||
#define MENU_HELPTEXT_MANUAL "Changes of this repository need to be committed by yourself"
|
||||
|
||||
/* --------------------------------------------
|
||||
* English messages
|
||||
* -------------------------------------------- */
|
||||
|
||||
#define MSG_FAIL_TO_START_SEAFILE "Failed to start Seafile"
|
||||
#define MSG_BROWSER_NOT_FOUND "Can't find your web browser"
|
||||
#define MSG_OPEN_URL_YOURSELF "Please open this url yourself"
|
||||
#define MSG_OPERATION_FAILED "Operation failed"
|
||||
|
||||
#endif
|
398
desktop/common/seaf-utils.c
Normal file
@ -0,0 +1,398 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "strbuf.h"
|
||||
#include "seaf-utils.h"
|
||||
#include "seaf-ext-log.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#include "../explorer/seaf-menu.h"
|
||||
#else
|
||||
#include "../nautilus/seaf-menu.h"
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef WIN32
|
||||
extern void send_refresh_menu_signal(NautilusMenuProvider *);
|
||||
#endif
|
||||
|
||||
typedef struct RepoInfo {
|
||||
char repo_id[37];
|
||||
char repo_wt[MAX_PATH];
|
||||
} RepoInfo;
|
||||
|
||||
struct repo_cache {
|
||||
RepoInfo *info;
|
||||
int n_repo;
|
||||
} ;
|
||||
|
||||
struct repo_cache repo_cache;
|
||||
|
||||
/* Mutex for repo cache r/w, and extension pipe r/w synchronization */
|
||||
#ifdef WIN32
|
||||
HANDLE cache_mutex;
|
||||
HANDLE pipe_mutex;
|
||||
#else
|
||||
#include <pthread.h>
|
||||
pthread_mutex_t cache_mutex;
|
||||
pthread_mutex_t pipe_mutex;
|
||||
#endif
|
||||
|
||||
|
||||
char *
|
||||
do_str_add (const char *s1, const char *s2)
|
||||
{
|
||||
if (!s1 && !s2)
|
||||
return NULL;
|
||||
|
||||
if (!s1)
|
||||
return strdup(s2);
|
||||
|
||||
if (!s2)
|
||||
return strdup(s1);
|
||||
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
|
||||
strbuf_addstr (&sb, s1);
|
||||
strbuf_addstr (&sb, s2);
|
||||
|
||||
return strbuf_detach (&sb, NULL);
|
||||
}
|
||||
|
||||
char*
|
||||
regulate_path(char *p)
|
||||
{
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
char *s = p;
|
||||
/* Use capitalized C/D/E, etc. */
|
||||
if (s[0] >= 'a')
|
||||
s[0] = toupper(s[0]);
|
||||
|
||||
/* Use / instead of \ */
|
||||
while (*s) {
|
||||
if (*s == '\\')
|
||||
*s = '/';
|
||||
s++;
|
||||
}
|
||||
|
||||
s--;
|
||||
/* strip trailing white spaces and path seperator */
|
||||
while (isspace(*s) || *s == '/') {
|
||||
*s = '\0';
|
||||
s--;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Tell if a dir is a subdir of an worktree path, we can't rely only on
|
||||
* strstr. For example, if /opt/my-repo is a worktree path, strstr will say
|
||||
* yes to /opt/my-repoxxx .
|
||||
*/
|
||||
static bool
|
||||
dir_worktree_match (char *dir, char *worktree)
|
||||
{
|
||||
if (str_case_str(dir, worktree) != dir)
|
||||
return FALSE;
|
||||
|
||||
int len = strlen(worktree);
|
||||
|
||||
if (dir[len] != '/' && dir[len] != '\0') {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
get_repo_id_wt (SeafMenu *seaf_menu)
|
||||
{
|
||||
if (!seaf_menu) {
|
||||
return;
|
||||
}
|
||||
|
||||
regulate_path(seaf_menu->name);
|
||||
update_repo_cache();
|
||||
|
||||
seaf_mutex_acquire(&cache_mutex, TRUE);
|
||||
|
||||
int i = 0;
|
||||
for (i = 0; i < repo_cache.n_repo; i++) {
|
||||
RepoInfo *p_info = &repo_cache.info[i];
|
||||
if (dir_worktree_match (seaf_menu->name, p_info->repo_wt)) {
|
||||
memcpy (seaf_menu->repo_id, p_info->repo_id, 37);
|
||||
memcpy (seaf_menu->repo_wt, p_info->repo_wt,
|
||||
strlen(p_info->repo_wt) + 1);
|
||||
break;
|
||||
} else {
|
||||
/* seaf_ext_log("Not match:\n[%s] [%s]", */
|
||||
/* seaf_menu->name, p_info->repo_wt); */
|
||||
}
|
||||
}
|
||||
|
||||
seaf_mutex_release(&cache_mutex);
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
#define REPO_CACHE_REFRESH_INTERVAL 3
|
||||
|
||||
static inline bool
|
||||
need_fresh (SYSTEMTIME *last_refresh, SYSTEMTIME *now)
|
||||
{
|
||||
if (now->wHour > last_refresh->wHour)
|
||||
return TRUE;
|
||||
|
||||
if (now->wMinute > last_refresh->wMinute)
|
||||
return TRUE;
|
||||
|
||||
if ((now->wSecond - last_refresh->wSecond) >= REPO_CACHE_REFRESH_INTERVAL)
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool
|
||||
is_repo_top_dir(char *dir)
|
||||
{
|
||||
bool ret = FALSE;
|
||||
|
||||
regulate_path(dir);
|
||||
|
||||
#ifdef WIN32
|
||||
static SYSTEMTIME last_refresh = { 0 };
|
||||
SYSTEMTIME now;
|
||||
GetLocalTime(&now);
|
||||
|
||||
if (need_fresh (&last_refresh, &now)) {
|
||||
update_repo_cache();
|
||||
last_refresh = now;
|
||||
}
|
||||
#else
|
||||
update_repo_cache();
|
||||
#endif
|
||||
seaf_mutex_acquire(&cache_mutex, TRUE);
|
||||
|
||||
int i = 0;
|
||||
|
||||
for (i = 0; i < repo_cache.n_repo; i++) {
|
||||
char *wt = repo_cache.info[i].repo_wt;
|
||||
if (strcmp (dir, wt) == 0) {
|
||||
ret = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
seaf_mutex_release(&cache_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
parse_id_worktree (char *line_in, char **repo_id, char **worktree)
|
||||
{
|
||||
char *line = strdup(line_in);
|
||||
char *ptr = line;
|
||||
char *orig = line;
|
||||
|
||||
/* skip spaces */
|
||||
while(isspace(*orig))
|
||||
orig++;
|
||||
|
||||
if (!orig)
|
||||
return FALSE;
|
||||
|
||||
/* The output of `seafile query' is "repo_id\tworktree", so we find the
|
||||
* first tab char and turn it to a NULL byte
|
||||
*/
|
||||
while (*ptr != '\t' && *ptr != '\0')
|
||||
ptr++;
|
||||
|
||||
if (*ptr == '\0')
|
||||
return FALSE;
|
||||
|
||||
*ptr = '\0';
|
||||
|
||||
*repo_id = strdup (orig);
|
||||
|
||||
if (strlen(*repo_id) != 36) {
|
||||
seaf_ext_log ("invalid length(%d) of repo_id : %s",
|
||||
strlen(*repo_id), *repo_id);
|
||||
free (*repo_id);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ptr++;
|
||||
|
||||
*worktree = strdup (ptr);
|
||||
|
||||
regulate_path (*worktree);
|
||||
|
||||
free(line);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int
|
||||
update_repo_cache()
|
||||
{
|
||||
static const char *request = "list-worktree";
|
||||
char *output = get_ext_pipe_response(request);
|
||||
|
||||
if (!output) {
|
||||
seaf_ext_log ("list-worktree returned NULL");
|
||||
return -1;
|
||||
|
||||
} else {
|
||||
/* split output into lines */
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
strbuf_addstr (&sb, output);
|
||||
struct strbuf **line_v = strbuf_split (&sb, '\n');
|
||||
strbuf_release (&sb);
|
||||
|
||||
/* first count repo numbers */
|
||||
int n_repo = 0;
|
||||
|
||||
struct strbuf **ptr = line_v;
|
||||
|
||||
while (*ptr) {n_repo++; ptr++;}
|
||||
|
||||
RepoInfo *rinfo = NULL;
|
||||
/* valid repo count */
|
||||
int n_valid = 0;
|
||||
|
||||
/* no repo */
|
||||
if (n_repo == 0) {
|
||||
seaf_ext_log ("No repos");
|
||||
if (repo_cache.n_repo != 0) {
|
||||
goto do_update;
|
||||
} else {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
rinfo = malloc(n_repo * (sizeof(RepoInfo)));
|
||||
|
||||
ptr = line_v;
|
||||
|
||||
while (*ptr) {
|
||||
|
||||
char *repo_id = NULL;
|
||||
char *repo_wt = NULL;
|
||||
|
||||
struct strbuf *line = *ptr;
|
||||
ptr++;
|
||||
|
||||
if (!parse_id_worktree (line->buf, &repo_id, &repo_wt))
|
||||
continue;
|
||||
|
||||
char *id = rinfo[n_valid].repo_id;
|
||||
char *wt = rinfo[n_valid].repo_wt;
|
||||
|
||||
memcpy (id, repo_id, 37);
|
||||
memcpy (wt, repo_wt, strlen(repo_wt) + 1);
|
||||
|
||||
n_valid++;
|
||||
|
||||
free(repo_id);
|
||||
free(repo_wt);
|
||||
}
|
||||
|
||||
do_update:
|
||||
/* accuqire the cache mutex, blocking */
|
||||
seaf_mutex_acquire(&cache_mutex, TRUE);
|
||||
|
||||
free (repo_cache.info);
|
||||
|
||||
repo_cache.info = rinfo;
|
||||
repo_cache.n_repo = n_valid;
|
||||
|
||||
seaf_mutex_release(&cache_mutex);
|
||||
|
||||
strbuf_list_free(line_v);
|
||||
|
||||
seaf_ext_log ("%d repos now", repo_cache.n_repo);
|
||||
}
|
||||
|
||||
out:
|
||||
free (output);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
bool seaf_ext_mutex_init()
|
||||
{
|
||||
bool ret = seaf_mutex_init(&cache_mutex);
|
||||
if (ret) {
|
||||
ret = seaf_mutex_init(&pipe_mutex);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
ext_pipe_common(const char *request, bool need_response)
|
||||
{
|
||||
seaf_mutex_acquire (&pipe_mutex, TRUE);
|
||||
int status = -1;
|
||||
char *result = NULL;
|
||||
if (ext_pipe_is_connected()) {
|
||||
status = send_ext_pipe_request_wrapper(request);
|
||||
if (status < 0 && !ext_pipe_is_connected()) {
|
||||
connect_ext_pipe();
|
||||
if (ext_pipe_is_connected()) {
|
||||
seaf_ext_log ("pipe reconnected OK");
|
||||
status = send_ext_pipe_request_wrapper(request);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
connect_ext_pipe();
|
||||
if (ext_pipe_is_connected()) {
|
||||
status = send_ext_pipe_request_wrapper(request);
|
||||
}
|
||||
}
|
||||
|
||||
if (!need_response) {
|
||||
/* TODO: on mingw64 gcc would think sizeof(long)=4 */
|
||||
seaf_mutex_release (&pipe_mutex);
|
||||
return (void *)(long)status;
|
||||
}
|
||||
|
||||
if (status < 0)
|
||||
goto failed;
|
||||
|
||||
result = read_ext_pipe_response();
|
||||
|
||||
failed:
|
||||
seaf_mutex_release (&pipe_mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
char *
|
||||
get_ext_pipe_response(const char *request)
|
||||
{
|
||||
if (!request)
|
||||
return NULL;
|
||||
return (char *)ext_pipe_common (request, TRUE);
|
||||
}
|
||||
|
||||
int
|
||||
send_ext_pipe_request (const char *request)
|
||||
{
|
||||
if (!request)
|
||||
return -1;
|
||||
return (int)(long)ext_pipe_common (request, FALSE);
|
||||
}
|
||||
|
44
desktop/common/seaf-utils.h
Normal file
@ -0,0 +1,44 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
#ifndef SEAF_UTILS_H
|
||||
#define SEAF_UTILS_H
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
struct SeafMenu;
|
||||
|
||||
/* void seaf_menu_ref(struct SeafMenu *seaf_menu); */
|
||||
/* void seaf_menu_unref(struct SeafMenu *seaf_menu); */
|
||||
|
||||
/* s = s1 + s2, free s when not using it anymore. */
|
||||
char *do_str_add (const char *s1, const char *s2);
|
||||
|
||||
/* Make all `path' in the shell extension use consistent style, such
|
||||
* as path seperator, captalized C/D/E for win32, etc.
|
||||
*/
|
||||
char* regulate_path(char *path);
|
||||
|
||||
|
||||
/* Analyse the current direcotry by querying the daemon. If its in a
|
||||
* repo dir, get the repo id and worktree path.
|
||||
*/
|
||||
void get_repo_id_wt (struct SeafMenu *seaf_menu);
|
||||
|
||||
/* Test whether dirname `dir' is a top repo dir */
|
||||
bool is_repo_top_dir(char *dir);
|
||||
|
||||
/* Initialize mutex for repo info cache */
|
||||
bool seaf_ext_mutex_init();
|
||||
|
||||
int update_repo_cache();
|
||||
|
||||
/* Send a request to ext pipe, but does not need the response */
|
||||
int send_ext_pipe_request (const char *request);
|
||||
|
||||
/* Send a request to ext pipe, and get the response back. The response has
|
||||
* already been converted to local encoding (GBK on windows). On error, NULL
|
||||
* is returned. Free the response string when done.
|
||||
*/
|
||||
char *get_ext_pipe_response(const char *request);
|
||||
|
||||
|
||||
#endif /* SEAF_UTILS_H */
|
435
desktop/common/strbuf.c
Normal file
@ -0,0 +1,435 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <fcntl.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
#include <io.h>
|
||||
#include <windows.h>
|
||||
|
||||
#else
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#include "strbuf.h"
|
||||
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
/*
|
||||
* The size parameter specifies the available space, i.e. includes
|
||||
* the trailing NUL byte; but Windows's vsnprintf uses the entire
|
||||
* buffer and avoids the trailing NUL, should the buffer be exactly
|
||||
* big enough for the result. Defining SNPRINTF_SIZE_CORR to 1 will
|
||||
* therefore remove 1 byte from the reported buffer size, so we
|
||||
* always have room for a trailing NUL byte.
|
||||
*/
|
||||
#ifndef SNPRINTF_SIZE_CORR
|
||||
#if defined(WIN32) && (!defined(__GNUC__) || __GNUC__ < 4)
|
||||
#define SNPRINTF_SIZE_CORR 1
|
||||
#else
|
||||
#define SNPRINTF_SIZE_CORR 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#undef vsnprintf
|
||||
int seaf_vsnprintf(char *str, size_t maxsize, const char *format, va_list ap)
|
||||
{
|
||||
char *s;
|
||||
int ret = -1;
|
||||
|
||||
if (maxsize > 0) {
|
||||
ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, ap);
|
||||
if (ret == maxsize-1)
|
||||
ret = -1;
|
||||
/* Windows does not NUL-terminate if result fills buffer */
|
||||
str[maxsize-1] = 0;
|
||||
}
|
||||
if (ret != -1)
|
||||
return ret;
|
||||
|
||||
s = NULL;
|
||||
if (maxsize < 128)
|
||||
maxsize = 128;
|
||||
|
||||
while (ret == -1) {
|
||||
maxsize *= 4;
|
||||
str = realloc(s, maxsize);
|
||||
if (! str)
|
||||
break;
|
||||
s = str;
|
||||
ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, ap);
|
||||
if (ret == maxsize-1)
|
||||
ret = -1;
|
||||
}
|
||||
free(s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int seaf_snprintf(char *str, size_t maxsize, const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int ret;
|
||||
|
||||
va_start(ap, format);
|
||||
ret = seaf_vsnprintf(str, maxsize, format, ap);
|
||||
va_end(ap);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define vsnprintf seaf_vsnprintf
|
||||
#define snprintf seaf_snprintf
|
||||
|
||||
#endif /* WIN32 */
|
||||
|
||||
static inline void die (char *msg)
|
||||
{
|
||||
if (msg)
|
||||
fprintf (stderr, "%s\n", msg);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
#define alloc_nr(x) (((x)+16)*3/2)
|
||||
|
||||
/*
|
||||
* Realloc the buffer pointed at by variable 'x' so that it can hold
|
||||
* at least 'nr' entries; the number of entries currently allocated
|
||||
* is 'alloc', using the standard growing factor alloc_nr() macro.
|
||||
*
|
||||
* DO NOT USE any expression with side-effect for 'x' or 'alloc'.
|
||||
*/
|
||||
#define ALLOC_GROW(x, nr, alloc) \
|
||||
do { \
|
||||
if ((nr) > alloc) { \
|
||||
if (alloc_nr(alloc) < (nr)) \
|
||||
alloc = (nr); \
|
||||
else \
|
||||
alloc = alloc_nr(alloc); \
|
||||
x = realloc((x), alloc * sizeof(*(x))); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
static inline char *strchrnul(const char *s, int c)
|
||||
{
|
||||
while (*s && *s != c)
|
||||
s++;
|
||||
return (char *)s;
|
||||
}
|
||||
|
||||
int prefixcmp(const char *str, const char *prefix)
|
||||
{
|
||||
for (; ; str++, prefix++)
|
||||
if (!*prefix)
|
||||
return 0;
|
||||
else if (*str != *prefix)
|
||||
return (unsigned char)*prefix - (unsigned char)*str;
|
||||
}
|
||||
|
||||
/*
|
||||
* Used as the default ->buf value, so that people can always assume
|
||||
* buf is non NULL and ->buf is NUL terminated even for a freshly
|
||||
* initialized strbuf.
|
||||
*/
|
||||
char strbuf_slopbuf[1];
|
||||
|
||||
void strbuf_init(struct strbuf *sb, size_t hint)
|
||||
{
|
||||
sb->alloc = sb->len = 0;
|
||||
sb->buf = strbuf_slopbuf;
|
||||
if (hint)
|
||||
strbuf_grow(sb, hint);
|
||||
}
|
||||
|
||||
void strbuf_release(struct strbuf *sb)
|
||||
{
|
||||
if (sb->alloc) {
|
||||
free(sb->buf);
|
||||
strbuf_init(sb, 0);
|
||||
}
|
||||
}
|
||||
|
||||
char *strbuf_detach(struct strbuf *sb, size_t *sz)
|
||||
{
|
||||
char *res = sb->alloc ? sb->buf : NULL;
|
||||
if (sz)
|
||||
*sz = sb->len;
|
||||
strbuf_init(sb, 0);
|
||||
return res;
|
||||
}
|
||||
|
||||
void strbuf_attach(struct strbuf *sb, void *buf, size_t len, size_t alloc)
|
||||
{
|
||||
strbuf_release(sb);
|
||||
sb->buf = buf;
|
||||
sb->len = len;
|
||||
sb->alloc = alloc;
|
||||
strbuf_grow(sb, 0);
|
||||
sb->buf[sb->len] = '\0';
|
||||
}
|
||||
|
||||
void strbuf_grow(struct strbuf *sb, size_t extra)
|
||||
{
|
||||
if (sb->len + extra + 1 <= sb->len)
|
||||
die("you want to use way too much memory");
|
||||
if (!sb->alloc)
|
||||
sb->buf = NULL;
|
||||
ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
|
||||
}
|
||||
|
||||
void strbuf_trim(struct strbuf *sb)
|
||||
{
|
||||
char *b = sb->buf;
|
||||
while (sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1]))
|
||||
sb->len--;
|
||||
while (sb->len > 0 && isspace(*b)) {
|
||||
b++;
|
||||
sb->len--;
|
||||
}
|
||||
memmove(sb->buf, b, sb->len);
|
||||
sb->buf[sb->len] = '\0';
|
||||
}
|
||||
void strbuf_rtrim(struct strbuf *sb)
|
||||
{
|
||||
while (sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1]))
|
||||
sb->len--;
|
||||
sb->buf[sb->len] = '\0';
|
||||
}
|
||||
|
||||
void strbuf_ltrim(struct strbuf *sb)
|
||||
{
|
||||
char *b = sb->buf;
|
||||
while (sb->len > 0 && isspace(*b)) {
|
||||
b++;
|
||||
sb->len--;
|
||||
}
|
||||
memmove(sb->buf, b, sb->len);
|
||||
sb->buf[sb->len] = '\0';
|
||||
}
|
||||
|
||||
void strbuf_tolower(struct strbuf *sb)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < sb->len; i++)
|
||||
sb->buf[i] = tolower(sb->buf[i]);
|
||||
}
|
||||
|
||||
struct strbuf **strbuf_split(const struct strbuf *sb, int delim)
|
||||
{
|
||||
int alloc = 2, pos = 0;
|
||||
char *n, *p;
|
||||
struct strbuf **ret;
|
||||
struct strbuf *t;
|
||||
|
||||
ret = calloc(alloc, sizeof(struct strbuf *));
|
||||
p = n = sb->buf;
|
||||
while (n < sb->buf + sb->len) {
|
||||
int len;
|
||||
n = memchr(n, delim, sb->len - (n - sb->buf));
|
||||
if (pos + 1 >= alloc) {
|
||||
alloc = alloc * 2;
|
||||
ret = realloc(ret, sizeof(struct strbuf *) * alloc);
|
||||
}
|
||||
if (!n)
|
||||
n = sb->buf + sb->len - 1;
|
||||
len = n - p + 1;
|
||||
t = malloc(sizeof(struct strbuf));
|
||||
strbuf_init(t, len);
|
||||
strbuf_add(t, p, len);
|
||||
ret[pos] = t;
|
||||
ret[++pos] = NULL;
|
||||
p = ++n;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void strbuf_list_free(struct strbuf **sbs)
|
||||
{
|
||||
struct strbuf **s = sbs;
|
||||
|
||||
while (*s) {
|
||||
strbuf_release(*s);
|
||||
free(*s++);
|
||||
}
|
||||
free(sbs);
|
||||
}
|
||||
|
||||
int strbuf_cmp(const struct strbuf *a, const struct strbuf *b)
|
||||
{
|
||||
int cmp;
|
||||
if (a->len < b->len) {
|
||||
cmp = memcmp(a->buf, b->buf, a->len);
|
||||
return cmp ? cmp : -1;
|
||||
} else {
|
||||
cmp = memcmp(a->buf, b->buf, b->len);
|
||||
return cmp ? cmp : a->len != b->len;
|
||||
}
|
||||
}
|
||||
|
||||
void strbuf_splice(struct strbuf *sb, size_t pos, size_t len,
|
||||
const void *data, size_t dlen)
|
||||
{
|
||||
if (pos + len < pos)
|
||||
die("you want to use way too much memory");
|
||||
if (pos > sb->len)
|
||||
die("`pos' is too far after the end of the buffer");
|
||||
if (pos + len > sb->len)
|
||||
die("`pos + len' is too far after the end of the buffer");
|
||||
|
||||
if (dlen >= len)
|
||||
strbuf_grow(sb, dlen - len);
|
||||
memmove(sb->buf + pos + dlen,
|
||||
sb->buf + pos + len,
|
||||
sb->len - pos - len);
|
||||
memcpy(sb->buf + pos, data, dlen);
|
||||
strbuf_setlen(sb, sb->len + dlen - len);
|
||||
}
|
||||
|
||||
void strbuf_insert(struct strbuf *sb, size_t pos, const void *data, size_t len)
|
||||
{
|
||||
strbuf_splice(sb, pos, 0, data, len);
|
||||
}
|
||||
|
||||
void strbuf_remove(struct strbuf *sb, size_t pos, size_t len)
|
||||
{
|
||||
strbuf_splice(sb, pos, len, NULL, 0);
|
||||
}
|
||||
|
||||
void strbuf_add(struct strbuf *sb, const void *data, size_t len)
|
||||
{
|
||||
strbuf_grow(sb, len);
|
||||
memcpy(sb->buf + sb->len, data, len);
|
||||
strbuf_setlen(sb, sb->len + len);
|
||||
}
|
||||
|
||||
void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len)
|
||||
{
|
||||
strbuf_grow(sb, len);
|
||||
memcpy(sb->buf + sb->len, sb->buf + pos, len);
|
||||
strbuf_setlen(sb, sb->len + len);
|
||||
}
|
||||
|
||||
void strbuf_addf(struct strbuf *sb, const char *fmt, ...)
|
||||
{
|
||||
int len;
|
||||
va_list ap;
|
||||
|
||||
if (!strbuf_avail(sb))
|
||||
strbuf_grow(sb, 64);
|
||||
va_start(ap, fmt);
|
||||
len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);
|
||||
va_end(ap);
|
||||
if (len < 0)
|
||||
die("your vsnprintf is broken");
|
||||
if ((size_t)len > strbuf_avail(sb)) {
|
||||
strbuf_grow(sb, len);
|
||||
va_start(ap, fmt);
|
||||
len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);
|
||||
va_end(ap);
|
||||
if ((size_t)len > strbuf_avail(sb)) {
|
||||
die("this should not happen, your snprintf is broken");
|
||||
}
|
||||
}
|
||||
strbuf_setlen(sb, sb->len + len);
|
||||
}
|
||||
|
||||
void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn,
|
||||
void *context)
|
||||
{
|
||||
for (;;) {
|
||||
const char *percent;
|
||||
size_t consumed;
|
||||
|
||||
percent = strchrnul(format, '%');
|
||||
strbuf_add(sb, format, percent - format);
|
||||
if (!*percent)
|
||||
break;
|
||||
format = percent + 1;
|
||||
|
||||
consumed = fn(sb, format, context);
|
||||
if (consumed)
|
||||
format += consumed;
|
||||
else
|
||||
strbuf_addch(sb, '%');
|
||||
}
|
||||
}
|
||||
|
||||
size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)
|
||||
{
|
||||
size_t res;
|
||||
|
||||
strbuf_grow(sb, size);
|
||||
res = fread(sb->buf + sb->len, 1, size, f);
|
||||
if (res > 0) {
|
||||
strbuf_setlen(sb, sb->len + res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint)
|
||||
{
|
||||
size_t oldlen = sb->len;
|
||||
|
||||
strbuf_grow(sb, hint ? hint : 8192);
|
||||
for (;;) {
|
||||
ssize_t cnt;
|
||||
|
||||
cnt = read(fd, sb->buf + sb->len, sb->alloc - sb->len - 1);
|
||||
if (cnt < 0) {
|
||||
strbuf_setlen(sb, oldlen);
|
||||
return -1;
|
||||
}
|
||||
if (!cnt)
|
||||
break;
|
||||
sb->len += cnt;
|
||||
strbuf_grow(sb, 8192);
|
||||
}
|
||||
|
||||
sb->buf[sb->len] = '\0';
|
||||
return sb->len - oldlen;
|
||||
}
|
||||
|
||||
int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
|
||||
{
|
||||
int ch;
|
||||
|
||||
strbuf_grow(sb, 0);
|
||||
if (feof(fp))
|
||||
return EOF;
|
||||
|
||||
strbuf_reset(sb);
|
||||
while ((ch = fgetc(fp)) != EOF) {
|
||||
if (ch == term)
|
||||
break;
|
||||
strbuf_grow(sb, 1);
|
||||
sb->buf[sb->len++] = ch;
|
||||
}
|
||||
if (ch == EOF && sb->len == 0)
|
||||
return EOF;
|
||||
|
||||
sb->buf[sb->len] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint)
|
||||
{
|
||||
int fd, len;
|
||||
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
len = strbuf_read(sb, fd, hint);
|
||||
close(fd);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
|
||||
return len;
|
||||
}
|
129
desktop/common/strbuf.h
Normal file
@ -0,0 +1,129 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
#ifndef STRBUF_H
|
||||
#define STRBUF_H
|
||||
|
||||
/*
|
||||
* Strbuf's can be use in many ways: as a byte array, or to store arbitrary
|
||||
* long, overflow safe strings.
|
||||
*
|
||||
* Strbufs has some invariants that are very important to keep in mind:
|
||||
*
|
||||
* 1. the ->buf member is always malloc-ed, hence strbuf's can be used to
|
||||
* build complex strings/buffers whose final size isn't easily known.
|
||||
*
|
||||
* It is NOT legal to copy the ->buf pointer away.
|
||||
* `strbuf_detach' is the operation that detachs a buffer from its shell
|
||||
* while keeping the shell valid wrt its invariants.
|
||||
*
|
||||
* 2. the ->buf member is a byte array that has at least ->len + 1 bytes
|
||||
* allocated. The extra byte is used to store a '\0', allowing the ->buf
|
||||
* member to be a valid C-string. Every strbuf function ensure this
|
||||
* invariant is preserved.
|
||||
*
|
||||
* Note that it is OK to "play" with the buffer directly if you work it
|
||||
* that way:
|
||||
*
|
||||
* strbuf_grow(sb, SOME_SIZE);
|
||||
* ... Here, the memory array starting at sb->buf, and of length
|
||||
* ... strbuf_avail(sb) is all yours, and you are sure that
|
||||
* ... strbuf_avail(sb) is at least SOME_SIZE.
|
||||
* strbuf_setlen(sb, sb->len + SOME_OTHER_SIZE);
|
||||
*
|
||||
* Of course, SOME_OTHER_SIZE must be smaller or equal to strbuf_avail(sb).
|
||||
*
|
||||
* Doing so is safe, though if it has to be done in many places, adding the
|
||||
* missing API to the strbuf module is the way to go.
|
||||
*
|
||||
* XXX: do _not_ assume that the area that is yours is of size ->alloc - 1
|
||||
* even if it's true in the current implementation. Alloc is somehow a
|
||||
* "private" member that should not be messed with.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
extern char strbuf_slopbuf[];
|
||||
struct strbuf {
|
||||
size_t alloc;
|
||||
size_t len;
|
||||
char *buf;
|
||||
};
|
||||
|
||||
#define STRBUF_INIT { 0, 0, strbuf_slopbuf }
|
||||
|
||||
/*----- strbuf life cycle -----*/
|
||||
void strbuf_init(struct strbuf *, size_t);
|
||||
void strbuf_release(struct strbuf *);
|
||||
char *strbuf_detach(struct strbuf *, size_t *);
|
||||
void strbuf_attach(struct strbuf *, void *, size_t, size_t);
|
||||
static inline void strbuf_swap(struct strbuf *a, struct strbuf *b) {
|
||||
struct strbuf tmp = *a;
|
||||
*a = *b;
|
||||
*b = tmp;
|
||||
}
|
||||
|
||||
/*----- strbuf size related -----*/
|
||||
static inline size_t strbuf_avail(const struct strbuf *sb) {
|
||||
return sb->alloc ? sb->alloc - sb->len - 1 : 0;
|
||||
}
|
||||
|
||||
void strbuf_grow(struct strbuf *, size_t);
|
||||
|
||||
static inline void strbuf_setlen(struct strbuf *sb, size_t len) {
|
||||
if (!sb->alloc)
|
||||
strbuf_grow(sb, 0);
|
||||
assert(len < sb->alloc);
|
||||
sb->len = len;
|
||||
sb->buf[len] = '\0';
|
||||
}
|
||||
#define strbuf_reset(sb) strbuf_setlen(sb, 0)
|
||||
|
||||
/*----- content related -----*/
|
||||
void strbuf_trim(struct strbuf *);
|
||||
void strbuf_rtrim(struct strbuf *);
|
||||
void strbuf_ltrim(struct strbuf *);
|
||||
int strbuf_cmp(const struct strbuf *, const struct strbuf *);
|
||||
void strbuf_tolower(struct strbuf *);
|
||||
|
||||
struct strbuf **strbuf_split(const struct strbuf *, int delim);
|
||||
void strbuf_list_free(struct strbuf **);
|
||||
|
||||
/*----- add data in your buffer -----*/
|
||||
static inline void strbuf_addch(struct strbuf *sb, int c) {
|
||||
strbuf_grow(sb, 1);
|
||||
sb->buf[sb->len++] = c;
|
||||
sb->buf[sb->len] = '\0';
|
||||
}
|
||||
|
||||
void strbuf_insert(struct strbuf *, size_t pos, const void *, size_t);
|
||||
void strbuf_remove(struct strbuf *, size_t pos, size_t len);
|
||||
|
||||
/* splice pos..pos+len with given data */
|
||||
void strbuf_splice(struct strbuf *, size_t pos, size_t len,
|
||||
const void *, size_t);
|
||||
|
||||
void strbuf_add(struct strbuf *, const void *, size_t);
|
||||
static inline void strbuf_addstr(struct strbuf *sb, const char *s) {
|
||||
strbuf_add(sb, s, strlen(s));
|
||||
}
|
||||
static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {
|
||||
strbuf_add(sb, sb2->buf, sb2->len);
|
||||
}
|
||||
void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len);
|
||||
|
||||
typedef size_t (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context);
|
||||
void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, void *context);
|
||||
|
||||
__attribute__((format(printf,2,3)))
|
||||
void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
|
||||
|
||||
size_t strbuf_fread(struct strbuf *, size_t, FILE *);
|
||||
/* XXX: if read fails, any partial read is undone */
|
||||
ssize_t strbuf_read(struct strbuf *, int fd, size_t hint);
|
||||
int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint);
|
||||
|
||||
int strbuf_getline(struct strbuf *, FILE *, int);
|
||||
|
||||
void stripspace(struct strbuf *buf, int skip_comments);
|
||||
int launch_editor(const char *path, struct strbuf *buffer, const char *const *env);
|
||||
|
||||
#endif /* STRBUF_H */
|
137
desktop/explorer/README.org
Normal file
@ -0,0 +1,137 @@
|
||||
#+ -*- mode: org; fill-column: 70; -*-
|
||||
#+title: Shell Extension for Seafile using COM
|
||||
#+startup: showall
|
||||
|
||||
|
||||
* Useful Links
|
||||
|
||||
Introduction to COM - What It Is and How to Use It.
|
||||
|
||||
http://www.codeproject.com/KB/COM/comintro.aspx
|
||||
|
||||
COM in plain C
|
||||
|
||||
http://www.codeproject.com/KB/COM/com_in_c1.aspx
|
||||
|
||||
Creating shell extension handlers
|
||||
|
||||
http://msdn.microsoft.com/en-us/library/bb776797.aspx
|
||||
|
||||
Creating context menu handlers
|
||||
|
||||
http://msdn.microsoft.com/en-us/library/bb776881.aspx
|
||||
|
||||
git-cheetah project
|
||||
|
||||
(git-cheetah contains shell extension for Git-windows/Nautilus/MacOS Finder)
|
||||
|
||||
http://repo.or.cz/w/git-cheetah.git
|
||||
|
||||
|
||||
* What is Shell
|
||||
|
||||
Windows Shell is just the Windows Explorer, i.e. *explorer.exe*.
|
||||
|
||||
* What is COM
|
||||
|
||||
+ COM is short for "Component Object Model"
|
||||
+ COM is always in the form of a DLL with several specific rules.
|
||||
+ A DLL is called a *COM Server*, while the one using the dll is
|
||||
called a *COM client*.
|
||||
|
||||
In shell extension, we are writing a COM Server, and the Windows
|
||||
Shell is our client. For example, for a menu handler, which is used
|
||||
to add a customized menu item, the Winodws Explorer calls some
|
||||
function in our COM with a param descrbing the current file for whom
|
||||
the menu is being displayed. In our COM implementation, we decide
|
||||
what menu items to add by analysing this param, and use shell API
|
||||
like =InsertMenuItem= to add menu items.
|
||||
|
||||
* How to implement a COM
|
||||
|
||||
COM can be implented in any programming language which has a concept
|
||||
of *function pointer*. In practice, people mainly use C++, but C is
|
||||
also applicable. We use C.
|
||||
|
||||
|
||||
* How do Shell know which function in the interface to call
|
||||
|
||||
Our menu items are to be added in two steps:
|
||||
|
||||
1) Shell queries you where is the handler function, and you give a
|
||||
pointer to it;
|
||||
2) Shell calls this function using that pointer and corresponding
|
||||
args.
|
||||
|
||||
|
||||
To be concrete, you provide a =QueryInterface()= function, and
|
||||
accept a GUID as one of the params. In the implementation of this
|
||||
function, if the guid param is =IID_IContextMenu=, you just return
|
||||
the func pointer to the context menu hanlder, and the shell will use
|
||||
that pointer to call your menu hanlder, which is your main routine
|
||||
to add your customized menu items, later on.
|
||||
|
||||
|
||||
* Control flow of the display a customized menu
|
||||
|
||||
1) users right click
|
||||
|
||||
2) shell finds the registration info in system registry and gets to
|
||||
know we are intereseted to add customized menu items
|
||||
|
||||
3) shell loads the dll, and calls Dll's GetClassObject function,
|
||||
which return a pointer to dll's *factory class*.
|
||||
|
||||
4) shell calls factory's CreateInstance method to create a object of
|
||||
the extension, i.e. seaf_shell_ext.
|
||||
|
||||
5) shell calls seaf_shell_ext->vtable->initialize with args to tell
|
||||
the extension about the calling enviroment: whether the click is
|
||||
a background click, or a click on a file; and supply the file
|
||||
object or a folder object as well.
|
||||
|
||||
6) shell asks the extension object for the IContextMenu Interface,
|
||||
i.e. calls its QueryInterface method with IID_IContextMenu as a
|
||||
param. The object returns a pointer to the seaf_menu structure.
|
||||
|
||||
7) shell calls the seaf_menu->vtable->QueryContextMenu, and we
|
||||
decide *what to add to the menu.*
|
||||
|
||||
|
||||
|
||||
* Module Organization
|
||||
|
||||
|-----------------------+-----------------------------------------------------|
|
||||
| file (.[ch]) | content |
|
||||
|-----------------------+-----------------------------------------------------|
|
||||
| seaf-dll | standard COM dll routines, registry operation |
|
||||
|-----------------------+-----------------------------------------------------|
|
||||
| seaf-ext | standard shell extension routines |
|
||||
|-----------------------+-----------------------------------------------------|
|
||||
| seaf-factory | implemented IClassFactory interface |
|
||||
|-----------------------+-----------------------------------------------------|
|
||||
| seaf-menu | implemented IContextMenu interface |
|
||||
|-----------------------+-----------------------------------------------------|
|
||||
| ../common/menu-engine | context menu handler backend |
|
||||
|-----------------------+-----------------------------------------------------|
|
||||
| seaf-icon | Icon handler |
|
||||
|-----------------------+-----------------------------------------------------|
|
||||
| ../common/seaf-utils | helper functions |
|
||||
|-----------------------+-----------------------------------------------------|
|
||||
| seaf-exec | exec a child process and get its stdout/stderr |
|
||||
|-----------------------+-----------------------------------------------------|
|
||||
| platform | os dependent functions |
|
||||
|-----------------------+-----------------------------------------------------|
|
||||
| ../common/strbuf | a string manipulating module, like GString |
|
||||
|-----------------------+-----------------------------------------------------|
|
||||
| registry | create/delete registry entries, for (un)registering |
|
||||
|-----------------------+-----------------------------------------------------|
|
||||
| seaf_ext_rc | dialogs, icons, created using MS visual studio 2008 |
|
||||
|-----------------------+-----------------------------------------------------|
|
||||
| (un)register.bat | scripts for (un)registering the shell extension |
|
||||
|-----------------------+-----------------------------------------------------|
|
||||
|
||||
|
||||
* Others
|
||||
|
||||
For compiling and testing, see desktop/README
|
766
desktop/explorer/platform.c
Normal file
@ -0,0 +1,766 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#include <io.h>
|
||||
#include <shellapi.h>
|
||||
#include <shlwapi.h>
|
||||
#include <fcntl.h>
|
||||
#include <psapi.h>
|
||||
#include <ctype.h>
|
||||
#include <userenv.h>
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
|
||||
#include "seaf-dll.h"
|
||||
#include "seaf-menu.h"
|
||||
#include "menu-engine.h"
|
||||
|
||||
#include "seaf-ext-log.h"
|
||||
#include "strbuf.h"
|
||||
#include "seaf-utils.h"
|
||||
|
||||
|
||||
const char *get_home_dir()
|
||||
{
|
||||
static char *home;
|
||||
|
||||
if (home)
|
||||
return home;
|
||||
|
||||
char buf[MAX_PATH] = {'\0'};
|
||||
|
||||
if (!home) {
|
||||
/* Try env variable first. */
|
||||
GetEnvironmentVariable("HOME", buf, MAX_PATH);
|
||||
if (buf[0] != '\0')
|
||||
home = strdup(buf);
|
||||
}
|
||||
|
||||
if (!home) {
|
||||
/* No `HOME' ENV; Try user profile */
|
||||
HANDLE hToken = NULL;
|
||||
DWORD len = MAX_PATH;
|
||||
if (OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
|
||||
GetUserProfileDirectory (hToken, buf, &len);
|
||||
CloseHandle(hToken);
|
||||
if (buf[0] != '\0')
|
||||
home = strdup(buf);
|
||||
}
|
||||
}
|
||||
|
||||
if (home)
|
||||
regulate_path(home);
|
||||
|
||||
return home;
|
||||
}
|
||||
|
||||
static char*
|
||||
seaf_iconv (const char *src, UINT from_encoding, UINT to_encoding)
|
||||
{
|
||||
if (!src)
|
||||
return NULL;
|
||||
|
||||
char *dst = NULL;
|
||||
int len, res;
|
||||
|
||||
len = res = 0;
|
||||
/* first get wchar length of the src str */
|
||||
len = MultiByteToWideChar
|
||||
(from_encoding, /* multibyte code page */
|
||||
0, /* flags */
|
||||
src, /* src */
|
||||
-1, /* src len, -1 for all including \0 */
|
||||
NULL, /* dst */
|
||||
0); /* dst buf len */
|
||||
|
||||
if (len <= 0)
|
||||
return NULL;
|
||||
|
||||
wchar_t *tmp_wchar = malloc (sizeof(wchar_t) * len);
|
||||
res = MultiByteToWideChar
|
||||
(from_encoding, /* multibyte code page */
|
||||
0, /* flags */
|
||||
src, /* src */
|
||||
-1, /* src len, -1 for all includes \0 */
|
||||
tmp_wchar, /* dst */
|
||||
len); /* dst buf len */
|
||||
|
||||
if (res <= 0) {
|
||||
free (tmp_wchar);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Now we have the widechar, we can convert it into dst */
|
||||
/* first get dst str length */
|
||||
len = WideCharToMultiByte
|
||||
(to_encoding, /* multibyte code page */
|
||||
0, /* flags */
|
||||
tmp_wchar, /* src */
|
||||
-1, /* src len, -1 for all includes \0 */
|
||||
NULL, /* dst */
|
||||
0, /* dst buf len */
|
||||
NULL, /* default char */
|
||||
NULL); /* BOOL flag indicates default char is used */
|
||||
|
||||
if (len <= 0) {
|
||||
free (tmp_wchar);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dst = malloc (sizeof(char) * len);
|
||||
res = WideCharToMultiByte
|
||||
(to_encoding, /* multibyte code page */
|
||||
0, /* flags */
|
||||
tmp_wchar, /* src */
|
||||
-1, /* src len, -1 for all includes \0 */
|
||||
dst, /* dst */
|
||||
len, /* dst buf len */
|
||||
NULL, /* default char */
|
||||
NULL); /* BOOL flag indicates default char is used */
|
||||
|
||||
free (tmp_wchar);
|
||||
if (res <= 0) {
|
||||
free(dst);
|
||||
free(tmp_wchar);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
inline char *locale_from_utf8 (const char *src)
|
||||
{
|
||||
return seaf_iconv (src, CP_UTF8, CP_ACP);
|
||||
}
|
||||
|
||||
inline char *locale_to_utf8 (const char *src)
|
||||
{
|
||||
return seaf_iconv (src, CP_ACP, CP_UTF8);
|
||||
}
|
||||
|
||||
char *wchar_to_char (const wchar_t *src)
|
||||
{
|
||||
char dst[MAX_PATH];
|
||||
int len;
|
||||
|
||||
len = WideCharToMultiByte
|
||||
(CP_ACP, /* multibyte code page */
|
||||
0, /* flags */
|
||||
src, /* src */
|
||||
-1, /* src len, -1 for all includes \0 */
|
||||
dst, /* dst */
|
||||
MAX_PATH, /* dst buf len */
|
||||
NULL, /* default char */
|
||||
NULL); /* BOOL flag indicates default char is used */
|
||||
|
||||
if (len <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return strdup(dst);
|
||||
}
|
||||
|
||||
wchar_t *char_to_wchar (const char *src)
|
||||
{
|
||||
wchar_t dst[MAX_PATH];
|
||||
int len;
|
||||
|
||||
len = MultiByteToWideChar
|
||||
(CP_ACP, /* multibyte code page */
|
||||
0, /* flags */
|
||||
src, /* src */
|
||||
-1, /* src len, -1 for all includes \0 */
|
||||
dst, /* dst */
|
||||
MAX_PATH); /* dst buf len */
|
||||
|
||||
if (len <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return wcsdup(dst);
|
||||
}
|
||||
|
||||
char *get_folder_path (const char *path)
|
||||
{
|
||||
if (!path || (access(path, F_OK) != 0))
|
||||
return NULL;
|
||||
|
||||
if (GetFileAttributes(path) & FILE_ATTRIBUTE_DIRECTORY)
|
||||
return strdup(path);
|
||||
|
||||
/* Note: here we are sure that the path would be regulated by
|
||||
regulate_path() sometime before, so we can test '/' only
|
||||
*/
|
||||
const char *ptr = strrchr(path, '/');
|
||||
|
||||
if (!ptr)
|
||||
return NULL;
|
||||
else {
|
||||
int len = ptr - path;
|
||||
char *s = malloc (len+2);
|
||||
memcpy (s, path, len+1);
|
||||
s[len+1] = '\0';
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
char *
|
||||
get_base_name (const char *path_in)
|
||||
{
|
||||
if (!path_in)
|
||||
return NULL;
|
||||
|
||||
char path[MAX_PATH];
|
||||
memcpy (path, path_in, strlen(path_in) + 1);
|
||||
|
||||
char *ptr = path;
|
||||
|
||||
while (*ptr)
|
||||
ptr++;
|
||||
|
||||
ptr--;
|
||||
while (*ptr == '/') {
|
||||
*ptr = '\0';
|
||||
ptr--;
|
||||
}
|
||||
|
||||
while (ptr > path && *ptr != '/')
|
||||
ptr--;
|
||||
|
||||
if (ptr == path) {
|
||||
return strdup(path);
|
||||
} else {
|
||||
char *s = strdup(ptr + 1);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void open_browser(char *url)
|
||||
{
|
||||
ShellExecute (NULL, "open", url, NULL,
|
||||
NULL, SW_SHOWNORMAL);
|
||||
|
||||
}
|
||||
|
||||
const char *get_this_dll_folder()
|
||||
{
|
||||
static char *dll_folder;
|
||||
|
||||
if (!dll_folder) {
|
||||
const char *dllpath = get_this_dll_filename();
|
||||
dll_folder = get_folder_path (dllpath);
|
||||
}
|
||||
|
||||
return dll_folder;
|
||||
}
|
||||
|
||||
|
||||
const char *get_this_dll_filename()
|
||||
{
|
||||
static char module_filename[MAX_PATH] = { '\0' };
|
||||
|
||||
if (module_filename[0] == '\0') {
|
||||
DWORD module_size;
|
||||
|
||||
module_size = GetModuleFileName(dll_hInst,
|
||||
module_filename, MAX_PATH);
|
||||
if (!module_size)
|
||||
return NULL;
|
||||
|
||||
regulate_path(module_filename);
|
||||
}
|
||||
|
||||
return module_filename;
|
||||
}
|
||||
|
||||
/* Use a wrapper in case we need to extend it in the future */
|
||||
static DWORD WINAPI job_thread_wrapper (void *vdata)
|
||||
{
|
||||
SeafExtJob *job = vdata;
|
||||
|
||||
int status = job->thread_func(job->data);
|
||||
|
||||
free (job);
|
||||
|
||||
ExitThread(status == 0);
|
||||
|
||||
}
|
||||
|
||||
int
|
||||
seaf_ext_start_thread(SeafThreadFunc thread_func, void *data, void *p_handle)
|
||||
{
|
||||
SeafExtJob *job = malloc (sizeof(SeafExtJob));
|
||||
|
||||
memset(job, 0, sizeof(SeafExtJob));
|
||||
|
||||
job->thread_func = thread_func;
|
||||
job->data = data;
|
||||
|
||||
DWORD tid = 0;
|
||||
|
||||
HANDLE hThread = CreateThread
|
||||
(NULL, /* security attr */
|
||||
0, /* stack size, 0 for default */
|
||||
(LPTHREAD_START_ROUTINE)job_thread_wrapper, /* start address */
|
||||
(void *)job, /* param*/
|
||||
0, /* creation flags */
|
||||
&tid); /* thread ID */
|
||||
|
||||
if (!hThread) {
|
||||
seaf_ext_log ("failed to create thread");
|
||||
free(job);
|
||||
return -1;
|
||||
}
|
||||
|
||||
HANDLE *p = p_handle;
|
||||
|
||||
if (p) {
|
||||
*p = hThread;
|
||||
} else {
|
||||
/* Close the handle so that the thread object is destroyed by
|
||||
* system when it terminates
|
||||
*/
|
||||
CloseHandle(hThread);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Try to accuqire the `mutex', blocking if `blocking' param is TRUE.
|
||||
* When `blocking' is TRUE, this funciton won't return until the mutex
|
||||
* is accquired. When `blocking' is FALSE, return immediately with the
|
||||
* return value indicating whether the mutex is accquired or not.
|
||||
*/
|
||||
inline bool seaf_mutex_acquire(void *vmutex, bool blocking)
|
||||
{
|
||||
if (!vmutex)
|
||||
return FALSE;
|
||||
|
||||
HANDLE mutex = *(HANDLE *)vmutex;
|
||||
|
||||
DWORD ret = 0;
|
||||
|
||||
if (!blocking) {
|
||||
ret = WaitForSingleObject(mutex, 0);
|
||||
return (ret == WAIT_OBJECT_0);
|
||||
}
|
||||
|
||||
/* blocking */
|
||||
while (1) {
|
||||
ret = WaitForSingleObject(mutex, INFINITE);
|
||||
|
||||
if (ret == WAIT_OBJECT_0)
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inline bool seaf_mutex_release(void *vmutex)
|
||||
{
|
||||
HANDLE mutex = *(HANDLE *)vmutex;
|
||||
ReleaseMutex(mutex);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
bool seaf_mutex_init (void *vmutex)
|
||||
{
|
||||
HANDLE mutex = NULL;
|
||||
|
||||
mutex = CreateMutex
|
||||
(NULL, /* securitry attr */
|
||||
FALSE, /* own the mutex immediately after create */
|
||||
NULL); /* name */
|
||||
|
||||
if (!mutex) {
|
||||
seaf_ext_log ("create cache_mutex failed, error code %u",
|
||||
(unsigned int)GetLastError());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
HANDLE *p = vmutex;
|
||||
|
||||
*p = mutex;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static char *
|
||||
do_uri_escape (const char *input)
|
||||
{
|
||||
if (!input)
|
||||
return NULL;
|
||||
|
||||
char buf[MAX_PATH];
|
||||
int len = strlen(input);
|
||||
int i = 0;
|
||||
int pos = 0;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (isascii(input[i])) {
|
||||
buf[pos++] = input[i];
|
||||
}
|
||||
else {
|
||||
int l = snprintf
|
||||
(buf + pos, MAX_PATH - pos,
|
||||
"%%%02X", (unsigned int)(unsigned char)input[i]);
|
||||
pos += l;
|
||||
}
|
||||
}
|
||||
buf[pos] = '\0';
|
||||
|
||||
return strdup(buf);
|
||||
}
|
||||
|
||||
char *seaf_uri_escape(const char *path)
|
||||
{
|
||||
char *u8_uri = locale_to_utf8(path);
|
||||
if (!u8_uri) return NULL;
|
||||
|
||||
char *escaped = do_uri_escape (u8_uri);
|
||||
free (u8_uri);
|
||||
|
||||
return escaped;
|
||||
}
|
||||
|
||||
static bool ext_pipe_connected = FALSE;
|
||||
static HANDLE ext_pipe = INVALID_HANDLE_VALUE;
|
||||
static OVERLAPPED ol;
|
||||
|
||||
inline bool
|
||||
ext_pipe_is_connected ()
|
||||
{
|
||||
return ext_pipe_connected;
|
||||
}
|
||||
|
||||
#define SEAF_EXT_PIPE_NAME "\\\\.\\pipe\\seafile_ext_pipe"
|
||||
|
||||
#define PIPE_BUFSIZE 1024
|
||||
#define PIPE_WRITE_WAIT_TIME 1
|
||||
#define PIPE_READ_WAIT_TIME 1
|
||||
|
||||
int
|
||||
connect_ext_pipe ()
|
||||
{
|
||||
if (ext_pipe != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle (ext_pipe);
|
||||
}
|
||||
ext_pipe = CreateFile(
|
||||
SEAF_EXT_PIPE_NAME, // pipe name
|
||||
GENERIC_READ | // read and write access
|
||||
GENERIC_WRITE,
|
||||
0, // no sharing
|
||||
NULL, // default security attributes
|
||||
OPEN_EXISTING, // opens existing pipe
|
||||
FILE_FLAG_OVERLAPPED, // default attributes
|
||||
NULL); // no template file
|
||||
|
||||
if (ext_pipe == INVALID_HANDLE_VALUE) {
|
||||
/* seaf_ext_log ("Failed to create named pipe, GLE=%lu\n", GetLastError()); */
|
||||
ext_pipe_connected = FALSE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ext_pipe_connected = TRUE;
|
||||
seaf_ext_log ("[pipe] Ext pipe connected.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
seaf_ext_pipe_prepare()
|
||||
{
|
||||
memset(&ol, 0, sizeof(ol));
|
||||
HANDLE h_ev = CreateEvent
|
||||
(NULL, /* security attribute */
|
||||
FALSE, /* manual reset */
|
||||
FALSE, /* initial state */
|
||||
NULL); /* event name */
|
||||
|
||||
if (!h_ev) {
|
||||
return -1;
|
||||
}
|
||||
ol.hEvent = h_ev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void
|
||||
reset_overlapped()
|
||||
{
|
||||
ol.Offset = ol.OffsetHigh = 0;
|
||||
ResetEvent(ol.hEvent);
|
||||
}
|
||||
|
||||
/* Check error status after WriteFile/ReadFile */
|
||||
static bool
|
||||
check_last_error (BOOL ret)
|
||||
{
|
||||
DWORD last_error = GetLastError();
|
||||
if (!ret && (last_error != ERROR_IO_PENDING && last_error != ERROR_SUCCESS)) {
|
||||
if (last_error == ERROR_BROKEN_PIPE || last_error == ERROR_NO_DATA
|
||||
|| last_error == ERROR_PIPE_NOT_CONNECTED) {
|
||||
seaf_ext_log ("[ext pipe] pipe broken with error: %lu", last_error);
|
||||
ext_pipe_connected = FALSE;
|
||||
} else {
|
||||
seaf_ext_log ("[ext pipe] failed to WriteFile(), GLE=%lu", last_error);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Blocking waiting for ReadFile/WriteFile to finish with some timeout limit */
|
||||
static bool
|
||||
do_pipe_wait (HANDLE hPipe, OVERLAPPED *ol, DWORD len)
|
||||
{
|
||||
DWORD bytesRW, result;
|
||||
|
||||
result = WaitForSingleObject (ol->hEvent, PIPE_WRITE_WAIT_TIME * 1000);
|
||||
|
||||
if (result == WAIT_OBJECT_0) {
|
||||
if (GetLastError() == ERROR_IO_PENDING) {
|
||||
|
||||
/* seaf_ext_log ("After WaitForSingleObject(), GLE = ERROR_IO_PENDING"); */
|
||||
|
||||
if (!GetOverlappedResult(hPipe, ol, &bytesRW, FALSE)
|
||||
|| bytesRW != len) {
|
||||
seaf_ext_log ("[pipe write ] GetOverlappedResult failed, GLE=%lu",
|
||||
GetLastError());
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (result == WAIT_TIMEOUT) {
|
||||
seaf_ext_log ("[ext pipe] timeout. GLE=%lu", GetLastError());
|
||||
return FALSE;
|
||||
|
||||
} else {
|
||||
seaf_ext_log ("[ext pipe] WaitForSingleObject error, GLE=%lu",
|
||||
GetLastError());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static int
|
||||
ext_pipe_writen (HANDLE hPipe, void *buf, uint32_t len)
|
||||
{
|
||||
reset_overlapped();
|
||||
BOOL ret;
|
||||
DWORD bytesWritten;
|
||||
|
||||
ret = WriteFile(
|
||||
hPipe, // handle to pipe
|
||||
buf, // buffer to write from
|
||||
(DWORD)len, // number of bytes to write
|
||||
&bytesWritten, // number of bytes written
|
||||
&ol); // overlapped IO
|
||||
|
||||
if (!check_last_error(ret))
|
||||
return -1;
|
||||
|
||||
if (!do_pipe_wait (hPipe, &ol, (DWORD)len))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
ext_pipe_readn (HANDLE hPipe, void *buf, uint32_t len)
|
||||
{
|
||||
reset_overlapped();
|
||||
DWORD bytesRead;
|
||||
bool ret;
|
||||
ret= ReadFile(
|
||||
hPipe, // handle to pipe
|
||||
buf, // buffer to write from
|
||||
(DWORD)len, // number of bytes to read
|
||||
&bytesRead, // number of bytes read
|
||||
&ol); // overlapped IO
|
||||
|
||||
if (!check_last_error(ret))
|
||||
return -1;
|
||||
|
||||
if (!do_pipe_wait (hPipe, &ol, (DWORD)len))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Send a requset to ext pipe. Assume lock is hold by the caller. */
|
||||
int
|
||||
send_ext_pipe_request_wrapper (const char *request_in)
|
||||
{
|
||||
char *request = locale_to_utf8 (request_in);
|
||||
uint32_t len = strlen(request) + 1;
|
||||
|
||||
if (ext_pipe_writen(ext_pipe, &len, sizeof(len)) < 0) {
|
||||
free (request);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ext_pipe_writen(ext_pipe, request, len) < 0) {
|
||||
free (request);
|
||||
return -1;
|
||||
}
|
||||
|
||||
free(request);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *
|
||||
read_ext_pipe_response ()
|
||||
{
|
||||
uint32_t len;
|
||||
if (ext_pipe_readn (ext_pipe, &len, sizeof(len)) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (len == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *buf = (char *)malloc (len);
|
||||
|
||||
if (ext_pipe_readn (ext_pipe, buf, len) < 0) {
|
||||
free (buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Convert from utf8 to local encoding. */
|
||||
char *output = locale_from_utf8 (buf);
|
||||
free (buf);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/* Get HANDLE to some process by name */
|
||||
static HANDLE
|
||||
get_process_handle (const char *process_name_in)
|
||||
{
|
||||
char name[256];
|
||||
if (strstr(process_name_in, ".exe")) {
|
||||
snprintf (name, sizeof(name), "%s", process_name_in);
|
||||
} else {
|
||||
snprintf (name, sizeof(name), "%s.exe", process_name_in);
|
||||
}
|
||||
|
||||
DWORD aProcesses[1024], cbNeeded, cProcesses;
|
||||
|
||||
if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded))
|
||||
return NULL;
|
||||
|
||||
/* Calculate how many process identifiers were returned. */
|
||||
cProcesses = cbNeeded / sizeof(DWORD);
|
||||
|
||||
HANDLE hProcess;
|
||||
HMODULE hMod;
|
||||
char process_name[MAX_PATH];
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < cProcesses; i++) {
|
||||
if(aProcesses[i] == 0)
|
||||
continue;
|
||||
hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, aProcesses[i]);
|
||||
if (!hProcess)
|
||||
continue;
|
||||
|
||||
if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded)) {
|
||||
GetModuleBaseName(hProcess, hMod, process_name,
|
||||
sizeof(process_name)/sizeof(char));
|
||||
}
|
||||
|
||||
if (strcasecmp(process_name, name) == 0)
|
||||
return hProcess;
|
||||
else {
|
||||
CloseHandle(hProcess);
|
||||
}
|
||||
}
|
||||
/* Not found */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Tell whether some process with given name is running */
|
||||
BOOL
|
||||
process_is_running (const char *process_name)
|
||||
{
|
||||
HANDLE proc_handle = get_process_handle(process_name);
|
||||
|
||||
if (proc_handle) {
|
||||
CloseHandle(proc_handle);
|
||||
return TRUE;
|
||||
} else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
kill_process (const char *process_name)
|
||||
{
|
||||
HANDLE proc_handle = get_process_handle(process_name);
|
||||
|
||||
if (proc_handle) {
|
||||
TerminateProcess(proc_handle, 0);
|
||||
CloseHandle(proc_handle);
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
spawn_process (char *cmdline_in, char *working_directory)
|
||||
{
|
||||
if (!cmdline_in)
|
||||
return -1;
|
||||
char *cmdline = strdup (cmdline_in);
|
||||
|
||||
STARTUPINFO si;
|
||||
PROCESS_INFORMATION pi;
|
||||
unsigned flags;
|
||||
BOOL success;
|
||||
|
||||
/* we want to execute seafile without crreating a console window */
|
||||
flags = CREATE_NO_WINDOW;
|
||||
|
||||
memset(&si, 0, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESTDHANDLES | STARTF_FORCEOFFFEEDBACK | STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow = SW_NORMAL;
|
||||
|
||||
memset(&pi, 0, sizeof(pi));
|
||||
|
||||
char old_wd[MAX_PATH];
|
||||
|
||||
/* save previous wd */
|
||||
GetCurrentDirectory(sizeof(old_wd), old_wd);
|
||||
/* set seafile wd */
|
||||
SetCurrentDirectory (get_this_dll_folder());
|
||||
|
||||
if (!working_directory) {
|
||||
working_directory = old_wd;
|
||||
}
|
||||
success = CreateProcess(NULL, cmdline, NULL, NULL, TRUE, flags,
|
||||
NULL, working_directory, &si, &pi);
|
||||
/* restore previous wd */
|
||||
SetCurrentDirectory (old_wd);
|
||||
|
||||
free (cmdline);
|
||||
if (!success) {
|
||||
seaf_ext_log ("failed to fork_process: GLE=%lu\n", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* close the handle of thread so that the process object can be freed by
|
||||
* system
|
||||
*/
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
return 0;
|
||||
}
|
110
desktop/explorer/registry.c
Normal file
@ -0,0 +1,110 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
#include "platform.h"
|
||||
#include "registry.h"
|
||||
|
||||
/* uses get_registry_path to replace patterns */
|
||||
HRESULT create_reg_entries(const HKEY root, reg_value const info[])
|
||||
{
|
||||
HRESULT result;
|
||||
int i;
|
||||
|
||||
for (i = 0; NULL != info[i].path; ++i) {
|
||||
char path[MAX_REGISTRY_PATH];
|
||||
char name[MAX_REGISTRY_PATH], *regname = NULL;
|
||||
char value [MAX_REGISTRY_PATH], *regvalue = NULL;
|
||||
|
||||
HKEY key;
|
||||
DWORD disp;
|
||||
|
||||
get_registry_path(info[i].path, path);
|
||||
result = RegCreateKeyEx(root, path,
|
||||
0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_WOW64_64KEY, NULL,
|
||||
&key, &disp);
|
||||
if (ERROR_SUCCESS != result)
|
||||
return (result);
|
||||
|
||||
regname = get_registry_path(info[i].name, name);
|
||||
regvalue = get_registry_path(info[i].value, value);
|
||||
|
||||
/*
|
||||
* regname can legitimately be NULL,
|
||||
* but if value is NULL, it's just a key
|
||||
*/
|
||||
if (NULL != regvalue) {
|
||||
char *endptr;
|
||||
DWORD dwValue = strtoul(regvalue, &endptr, 10);
|
||||
if (endptr && !*endptr)
|
||||
result = RegSetValueEx(key, regname, 0,
|
||||
REG_DWORD,
|
||||
(LPBYTE)&dwValue,
|
||||
sizeof(dwValue));
|
||||
else
|
||||
result = RegSetValueEx(key, regname, 0,
|
||||
REG_SZ,
|
||||
(LPBYTE)regvalue,
|
||||
(DWORD)strlen(regvalue));
|
||||
}
|
||||
|
||||
RegCloseKey(key);
|
||||
if (ERROR_SUCCESS != result)
|
||||
return (result);
|
||||
}
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static inline HRESULT mask_errors(HRESULT const result)
|
||||
{
|
||||
switch (result) {
|
||||
case ERROR_FILE_NOT_FOUND: return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
HRESULT delete_reg_entries(HKEY const root, reg_value const info[])
|
||||
{
|
||||
HRESULT result;
|
||||
int i = 0;
|
||||
|
||||
/* count items in the array */
|
||||
while (NULL != info[i].path)
|
||||
i++;
|
||||
|
||||
/* walk the array backwards (we're at the terminating triple-null) */
|
||||
while (--i >= 0) {
|
||||
char path[MAX_REGISTRY_PATH];
|
||||
HKEY key;
|
||||
|
||||
get_registry_path(info[i].path, path);
|
||||
|
||||
if (info[i].name || info[i].value) {
|
||||
/* delete the value */
|
||||
|
||||
char name[MAX_REGISTRY_PATH], *regname = NULL;
|
||||
|
||||
result = mask_errors(RegOpenKeyEx(root, path,
|
||||
0, KEY_WRITE | KEY_WOW64_64KEY, &key));
|
||||
if (ERROR_SUCCESS != result)
|
||||
return result;
|
||||
|
||||
/*
|
||||
* some of our errors are masked (e.g. not found)
|
||||
* don't work on this key if we could not open it
|
||||
*/
|
||||
if (NULL == key)
|
||||
continue;
|
||||
|
||||
regname = get_registry_path(info[i].name, name);
|
||||
result = mask_errors(RegDeleteValue(key, regname));
|
||||
|
||||
RegCloseKey(key);
|
||||
} else /* not the value, delete the key */
|
||||
result = mask_errors(RegDeleteKey(root, path));
|
||||
|
||||
if (ERROR_SUCCESS != result)
|
||||
return (result);
|
||||
}
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
29
desktop/explorer/registry.h
Normal file
@ -0,0 +1,29 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This is basically a simplified regedit engine that supports
|
||||
* custom patterns through get_registry_path() that is required
|
||||
* to be provided by the clients.
|
||||
*
|
||||
* It attempts to convert values to LONG to create REG_DWORD values
|
||||
*/
|
||||
|
||||
#define MAX_REGISTRY_PATH MAX_PATH
|
||||
|
||||
#define CURRENT_WINDOWS "Software\\Microsoft\\Windows\\CurrentVersion\\"
|
||||
#define APPROVED_EXT "Shell Extensions\\Approved"
|
||||
#define CLASSES_ROOT "Software\\Classes\\"
|
||||
|
||||
typedef struct reg_value {
|
||||
char *path;
|
||||
char *name;
|
||||
char *value;
|
||||
} reg_value;
|
||||
|
||||
/*
|
||||
* Clients need to provide the implementation of this function.
|
||||
* The simplest implementation includes just strcpy(dst, src);
|
||||
*/
|
||||
char *get_registry_path(const char *src, char dst[MAX_REGISTRY_PATH]);
|
||||
|
||||
HRESULT create_reg_entries(const HKEY root, reg_value const info[]);
|
||||
HRESULT delete_reg_entries(HKEY const root, reg_value const info[]);
|
29
desktop/explorer/seaf-dlgs.c
Normal file
@ -0,0 +1,29 @@
|
||||
#include "platform.h"
|
||||
|
||||
#include "seaf-ext-log.h"
|
||||
#include "seaf-dlgs.h"
|
||||
#include "seaf-dll.h"
|
||||
#include "seaf-utils.h"
|
||||
#include "strbuf.h"
|
||||
|
||||
void msgbox_full(char *msg, GtkMessageType type, void *user_data)
|
||||
{
|
||||
UINT mtype = MB_OK;
|
||||
if (type == GTK_MESSAGE_WARNING)
|
||||
mtype |= MB_ICONWARNING;
|
||||
|
||||
MessageBox(NULL, msg, "Seafile", mtype);
|
||||
}
|
||||
|
||||
|
||||
bool msgbox_yes_or_no(char *question, void *user_data)
|
||||
{
|
||||
if (!question)
|
||||
return FALSE;
|
||||
|
||||
int res = MessageBox(NULL, question, "Seafile",
|
||||
MB_ICONQUESTION | MB_YESNO);
|
||||
|
||||
return (res == IDYES);
|
||||
}
|
||||
|
314
desktop/explorer/seaf-dll.c
Normal file
@ -0,0 +1,314 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
#include "platform.h"
|
||||
|
||||
#include <shlobj.h>
|
||||
#include <psapi.h>
|
||||
|
||||
#include "seaf-dll.h"
|
||||
#include "seaf-utils.h"
|
||||
#include "seaf-factory.h"
|
||||
#include "registry.h"
|
||||
#include "seaf-ext-log.h"
|
||||
#include "menu-engine.h"
|
||||
|
||||
const char *program_name = "Seafile";
|
||||
const char *program_version = "Seafile.Application.1";
|
||||
const char *program_id = "Seafile.Application";
|
||||
|
||||
volatile long object_count;
|
||||
volatile long lock_count;
|
||||
|
||||
HINSTANCE dll_hInst;
|
||||
|
||||
HRESULT PASCAL
|
||||
DllGetClassObject (REFCLSID obj_guid,
|
||||
REFIID factory_guid,
|
||||
void **factory_handle)
|
||||
{
|
||||
if (IsEqualCLSID(obj_guid, &CLSID_seaf_shell_ext)) {
|
||||
return class_factory_query_interface(&factory,
|
||||
factory_guid, factory_handle);
|
||||
}
|
||||
|
||||
*factory_handle = 0;
|
||||
return CLASS_E_CLASSNOTAVAILABLE;
|
||||
}
|
||||
|
||||
HRESULT PASCAL
|
||||
DllCanUnloadNow (void)
|
||||
{
|
||||
if (object_count == 0 && lock_count == 0) {
|
||||
return S_OK;
|
||||
} else {
|
||||
return S_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static char *whitelist[] = {
|
||||
"explorer.exe",
|
||||
"regsvr32.exe",
|
||||
"msiexec.exe",
|
||||
"verclsid.exe",
|
||||
NULL
|
||||
};
|
||||
|
||||
static BOOL
|
||||
is_allowed_process()
|
||||
{
|
||||
/* Only allow explorer and installer process to load this dll. Other process are refused, including:
|
||||
* 1. iexplore.exe
|
||||
* 2. firefox/chrome
|
||||
* 3. visual studio
|
||||
* 4. And many more ...
|
||||
*/
|
||||
char buf[MAX_PATH];
|
||||
if (!GetModuleBaseName(GetCurrentProcess(), NULL, buf, sizeof(buf))) {
|
||||
seaf_ext_log ("Failed to GetModuleBaseName(), GLE=%lu",
|
||||
GetLastError());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
seaf_ext_log ("checking for %s", buf);
|
||||
|
||||
char **ptr = whitelist;
|
||||
while (*ptr) {
|
||||
char *name = *ptr;
|
||||
if (strcasecmp(name, buf) == 0)
|
||||
return TRUE;
|
||||
ptr++;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOL WINAPI
|
||||
DllMain (HINSTANCE instance, DWORD reason, LPVOID reserved)
|
||||
{
|
||||
dll_hInst = instance;
|
||||
|
||||
if (reason == DLL_PROCESS_ATTACH) {
|
||||
|
||||
object_count = lock_count = 0;
|
||||
DisableThreadLibraryCalls(instance);
|
||||
|
||||
seaf_ext_log ("DllMain() called for ATTACH");
|
||||
|
||||
if (!is_allowed_process()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* init mutex */
|
||||
if(!seaf_ext_mutex_init()) {
|
||||
seaf_ext_log ("DllMain() failed because mutex init failed.");
|
||||
seaf_ext_log_stop ();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
seaf_ext_pipe_prepare();
|
||||
/* calc menu icons path according to user's installation path */
|
||||
translate_icon_paths();
|
||||
|
||||
} else if (reason == DLL_PROCESS_DETACH) {
|
||||
seaf_ext_log ("DllMain() called for DETACH");
|
||||
seaf_ext_log_stop ();
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* replaces a substring pattern with a string replacement within a string
|
||||
the replacement occurs in-place, hence string must be large enough to
|
||||
hold the result
|
||||
|
||||
the function does not handle recursive replacements, e.g.
|
||||
strreplace ("foo", "bar", "another bar");
|
||||
|
||||
always returns *string
|
||||
*/
|
||||
static char *
|
||||
strreplace(char *string, const size_t size,
|
||||
const char *pattern, const char *replacement)
|
||||
{
|
||||
size_t len = strlen(string);
|
||||
const size_t pattern_len = strlen(pattern);
|
||||
const size_t replace_len = strlen(replacement);
|
||||
|
||||
char *found = strstr(string, pattern);
|
||||
|
||||
while (found) {
|
||||
/* if the new len is greater than size, bail out */
|
||||
if (len + replace_len - pattern_len >= size)
|
||||
return string;
|
||||
|
||||
if (pattern_len != replace_len)
|
||||
memmove(found + replace_len,
|
||||
found + pattern_len,
|
||||
len - (found - string) - pattern_len + 1);
|
||||
memcpy(found, replacement, replace_len);
|
||||
len += replace_len - pattern_len;
|
||||
|
||||
found = strstr(string, pattern);
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
/*
|
||||
* The following is the data for our minimal regedit engine,
|
||||
* required for registration/unregistration of the extension
|
||||
*/
|
||||
#define CLASS_SEAFILE CLASSES_ROOT "CLSID\\@@CLSID@@"
|
||||
|
||||
#define CONTEXTMENUHANDLER "shellex\\ContextMenuHandlers\\@@PROGRAM_NAME@@"
|
||||
|
||||
#define SHELL_ICON_OVERLAY \
|
||||
CURRENT_WINDOWS "Explorer\\ShellIconOverlayIdentifiers\\000@@PROGRAM_NAME@@"
|
||||
|
||||
#define AUTO_START CURRENT_WINDOWS "Run"
|
||||
|
||||
/* as per "How to: Convert Between System::Guid and _GUID" */
|
||||
static const char *
|
||||
get_class_id()
|
||||
{
|
||||
static char class_id[MAX_REGISTRY_PATH] = { '\0' };
|
||||
|
||||
if (!*class_id) {
|
||||
GUID guid = CLSID_seaf_shell_ext;
|
||||
sprintf(class_id,
|
||||
"{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
|
||||
(unsigned int)guid.Data1, guid.Data2, guid.Data3,
|
||||
guid.Data4[0], guid.Data4[1], guid.Data4[2],
|
||||
guid.Data4[3], guid.Data4[4], guid.Data4[5],
|
||||
guid.Data4[6], guid.Data4[7]);
|
||||
}
|
||||
|
||||
return class_id;
|
||||
}
|
||||
|
||||
/* context menu handler can be registered in HKEY_CURRENT_USER */
|
||||
static const reg_value machine_registry_info[] = {
|
||||
{ CURRENT_WINDOWS APPROVED_EXT, "@@CLSID@@", "@@PROGRAM_NAME@@" },
|
||||
{ CURRENT_WINDOWS APPROVED_EXT "\\@@CLSID@@", NULL, NULL },
|
||||
{ CURRENT_WINDOWS APPROVED_EXT "\\@@CLSID@@", NULL,"@@PROGRAM_NAME@@" },
|
||||
|
||||
{ CLASS_SEAFILE, NULL, NULL },
|
||||
{ CLASS_SEAFILE, NULL, "@@PROGRAM_NAME@@" },
|
||||
{ CLASS_SEAFILE "\\InProcServer32", NULL, NULL },
|
||||
{ CLASS_SEAFILE "\\InProcServer32", NULL, "@@PROGRAM_PATH@@"},
|
||||
{ CLASS_SEAFILE "\\InProcServer32", "ThreadingModel", "Apartment" },
|
||||
|
||||
/* Menu extension */
|
||||
{ CLASSES_ROOT "Directory\\" CONTEXTMENUHANDLER, NULL, NULL },
|
||||
{ CLASSES_ROOT "Directory\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@" },
|
||||
{ CLASSES_ROOT "Directory\\Background\\" CONTEXTMENUHANDLER, NULL, NULL },
|
||||
{ CLASSES_ROOT "Directory\\Background\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@" },
|
||||
{ CLASSES_ROOT "Folder\\" CONTEXTMENUHANDLER, NULL, NULL },
|
||||
{ CLASSES_ROOT "Folder\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@" },
|
||||
|
||||
/* Icon extension */
|
||||
{ SHELL_ICON_OVERLAY, NULL, NULL },
|
||||
{ SHELL_ICON_OVERLAY, NULL, "@@CLSID@@" },
|
||||
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
/* Auto start info */
|
||||
static const reg_value auto_start_registry_info[] = {
|
||||
{ AUTO_START, "@@PROGRAM_NAME@@", "@@APPLET_PATH@@" },
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
/*
|
||||
* required by registry.c
|
||||
* supports @@PROGRAM_NAME@@, @@PROGRAM_PATH@@, @@CLSID@@ patterns
|
||||
*/
|
||||
char *
|
||||
get_registry_path(const char *src, char dst[MAX_REGISTRY_PATH])
|
||||
{
|
||||
if (NULL == src)
|
||||
return NULL;
|
||||
|
||||
strcpy(dst, src);
|
||||
|
||||
strreplace(dst, MAX_REGISTRY_PATH,
|
||||
"@@PROGRAM_NAME@@", program_name);
|
||||
|
||||
strreplace(dst, MAX_REGISTRY_PATH,
|
||||
"@@PROGRAM_PATH@@", get_this_dll_filename());
|
||||
|
||||
strreplace(dst, MAX_REGISTRY_PATH,
|
||||
"@@CLSID@@", get_class_id());
|
||||
|
||||
char applet_path[MAX_PATH] = {0};
|
||||
snprintf (applet_path, sizeof(applet_path), "\"%s%s\"",
|
||||
get_this_dll_folder(), "seafile-applet.exe");
|
||||
|
||||
char *p;
|
||||
for (p = applet_path; *p != '\0'; p++) {
|
||||
if (*p == '/')
|
||||
*p = '\\';
|
||||
}
|
||||
|
||||
strreplace(dst, MAX_REGISTRY_PATH,
|
||||
"@@APPLET_PATH@@", applet_path);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
HRESULT PASCAL
|
||||
DllInstall(BOOL bInstall, LPCWSTR pszCmdLine)
|
||||
{
|
||||
if (bInstall) {
|
||||
|
||||
create_reg_entries(HKEY_LOCAL_MACHINE, machine_registry_info);
|
||||
create_reg_entries(HKEY_CURRENT_USER, auto_start_registry_info);
|
||||
|
||||
kill_process("explorer.exe");
|
||||
Sleep(5000);
|
||||
|
||||
} else {
|
||||
delete_reg_entries(HKEY_LOCAL_MACHINE, machine_registry_info);
|
||||
delete_reg_entries(HKEY_CURRENT_USER, auto_start_registry_info);
|
||||
}
|
||||
|
||||
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST,NULL,NULL);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT PASCAL
|
||||
DllRegisterServer(void)
|
||||
{
|
||||
return DllInstall(TRUE, NULL);
|
||||
}
|
||||
|
||||
HRESULT PASCAL
|
||||
DllUnregisterServer(void)
|
||||
{
|
||||
return DllInstall(FALSE, NULL);
|
||||
}
|
||||
|
||||
#define S_WINDOW_NAME "seafile-applet"
|
||||
|
||||
/* UINT __stdcall TerminateSeafile(MSIHANDLE hModule) */
|
||||
UINT __stdcall TerminateSeafile(HANDLE hModule)
|
||||
{
|
||||
HWND hWnd = FindWindow(S_WINDOW_NAME, S_WINDOW_NAME);
|
||||
if (hWnd)
|
||||
{
|
||||
PostMessage(hWnd, WM_CLOSE, (WPARAM)NULL, (LPARAM)NULL);
|
||||
int i;
|
||||
for (i = 0; i < 10; ++i)
|
||||
{
|
||||
Sleep(500);
|
||||
if (!IsWindow(hWnd))
|
||||
{
|
||||
/* seafile-applet is now killed. */
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
/* seafile-applet is not running. */
|
||||
return ERROR_SUCCESS;
|
||||
}
|
35
desktop/explorer/seaf-dll.h
Normal file
@ -0,0 +1,35 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
#ifndef SEAF_DLL_H
|
||||
#define SEAF_DLL_H
|
||||
|
||||
static const CLSID CLSID_seaf_shell_ext = {
|
||||
0x84f93460, 0x1d70, 0x11e1,
|
||||
{0x99,0x91,0x00, 0x1e, 0x68, 0x03, 0x1d, 0xc7}};
|
||||
|
||||
/* Path of info stored in registry */
|
||||
|
||||
extern const char *program_name;
|
||||
extern const char *program_version;
|
||||
extern const char *program_id;
|
||||
|
||||
extern volatile long lock_count;
|
||||
extern volatile long object_count;
|
||||
|
||||
extern HINSTANCE dll_hInst;
|
||||
|
||||
HRESULT PASCAL
|
||||
DllGetClassObject(REFCLSID obj_guid, REFIID factory_guid, void **factory_handle);
|
||||
|
||||
HRESULT PASCAL
|
||||
DllCanUnloadNow(void);
|
||||
|
||||
HRESULT PASCAL
|
||||
DllRegisterServer(void);
|
||||
|
||||
HRESULT PASCAL
|
||||
DllUnregisterServer(void);
|
||||
|
||||
BOOL WINAPI
|
||||
DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved);
|
||||
|
||||
#endif /* SEAF_DLL_H */
|
105
desktop/explorer/seaf-factory.c
Normal file
@ -0,0 +1,105 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#include <shlobj.h>
|
||||
#include "seaf-factory.h"
|
||||
#include "seaf-dll.h"
|
||||
#include "seaf-menu.h"
|
||||
#include "seaf-icon.h"
|
||||
#include "seaf-ext-log.h"
|
||||
|
||||
/*
|
||||
* Since COM objects cannot be constructed like your traditional object (i.e.
|
||||
* with a proper constructor), they have to be constructed by another object,
|
||||
* the class factory.
|
||||
*
|
||||
* The class factory is an object which exists exactly once, and it cannot
|
||||
* be constructed or destroyed. Its sole purpose is to construct objects
|
||||
* given an interface.
|
||||
*/
|
||||
|
||||
STDMETHODIMP
|
||||
class_factory_query_interface(IClassFactory *this, REFIID guid, void **pointer)
|
||||
{
|
||||
if (!IsEqualIID(guid, &IID_IUnknown) &&
|
||||
!IsEqualIID(guid, &IID_IClassFactory)) {
|
||||
|
||||
*pointer = 0;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
*pointer = this;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static ULONG STDMETHODCALLTYPE
|
||||
return_one(IClassFactory *this)
|
||||
{
|
||||
return(1);
|
||||
}
|
||||
|
||||
static STDMETHODIMP
|
||||
create_instance(IClassFactory *this_, IUnknown *outer, REFIID guid, void **pointer)
|
||||
{
|
||||
*pointer = 0;
|
||||
|
||||
if (outer)
|
||||
return CLASS_E_NOAGGREGATION;
|
||||
|
||||
if (IsEqualIID(guid, &IID_IShellIconOverlayIdentifier)) {
|
||||
|
||||
*pointer = seaf_icon_overlay_new ();
|
||||
|
||||
} else if (IsEqualIID(guid, &IID_IContextMenu) ||
|
||||
IsEqualIID(guid, &IID_IContextMenu2) ||
|
||||
IsEqualIID(guid, &IID_IContextMenu3) ) {
|
||||
|
||||
*pointer = seaf_menu_new ();
|
||||
|
||||
} else if (IsEqualIID(guid, &IID_IShellExtInit)) {
|
||||
|
||||
SeafMenu *seaf_menu = seaf_menu_new ();
|
||||
*pointer = &seaf_menu->ishellextinit;
|
||||
|
||||
} else {
|
||||
char *s = "XXXX";
|
||||
|
||||
if (IsEqualIID (guid, &IID_IUnknown)) {
|
||||
s = "IUnknown";
|
||||
} else if (IsEqualIID (guid, &CLSID_seaf_shell_ext)) {
|
||||
s = "CLSID_seaf_shell_ext";
|
||||
}
|
||||
|
||||
seaf_ext_log ("No such interface : %s", s);
|
||||
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
InterlockedIncrement(&object_count);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static STDMETHODIMP
|
||||
lock_server(IClassFactory *this, BOOL lock)
|
||||
{
|
||||
if (lock)
|
||||
InterlockedIncrement(&lock_count);
|
||||
else
|
||||
InterlockedDecrement(&lock_count);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
IClassFactoryVtbl factory_virtual_table = {
|
||||
class_factory_query_interface,
|
||||
return_one,
|
||||
return_one,
|
||||
create_instance,
|
||||
lock_server
|
||||
};
|
||||
|
||||
IClassFactory factory = {
|
||||
&factory_virtual_table
|
||||
};
|
12
desktop/explorer/seaf-factory.h
Normal file
@ -0,0 +1,12 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
#ifndef SEAF_FACTORY_H
|
||||
#define SEAF_FACTORY_H
|
||||
|
||||
STDMETHODIMP
|
||||
class_factory_query_interface(IClassFactory *this,
|
||||
REFIID guid,
|
||||
void **pointer);
|
||||
|
||||
extern IClassFactory factory;
|
||||
|
||||
#endif /* SEAF_FACTORY_H */
|
163
desktop/explorer/seaf-icon.c
Normal file
@ -0,0 +1,163 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
|
||||
/* This module implemented `IShellIconOverlayIdentifier' interface, which add
|
||||
* a special icon overlay to any top level repo folder in Explorer. */
|
||||
|
||||
|
||||
#include "platform.h"
|
||||
#include <wchar.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "seaf-dll.h"
|
||||
#include "seaf-utils.h"
|
||||
#include "seaf-ext-log.h"
|
||||
|
||||
#include "seaf-icon.h"
|
||||
|
||||
struct IShellIconOverlay_Vtbl
|
||||
{
|
||||
STDMETHOD(QueryInterface)(void *, REFIID, PVOID*);
|
||||
STDMETHOD_(ULONG, AddRef)(void *);
|
||||
STDMETHOD_(ULONG, Release)(void *);
|
||||
STDMETHOD(IsMemberOf)(void *, LPCWSTR, DWORD);
|
||||
STDMETHOD(GetOverlayInfo)(void *, LPWSTR, int, int *, DWORD *);
|
||||
STDMETHOD(GetPriority)(void *, int *);
|
||||
|
||||
};
|
||||
|
||||
extern char *seaf_repo_ico;
|
||||
|
||||
static STDMETHODIMP
|
||||
GetOverlayInfo(void *p, LPWSTR pwszIconFile, int cchMax,
|
||||
int *pIndex, DWORD *pdwFlags)
|
||||
{
|
||||
if (!seaf_repo_ico) {
|
||||
seaf_ext_log ("failed to get seaf_repo_ico");
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
wchar_t *seaf_repo_ico_w = char_to_wchar(seaf_repo_ico);
|
||||
if (!seaf_repo_ico_w) {
|
||||
seaf_ext_log ("Convert seaf_repo_ico to wchar_t failed");
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
int wlen = wcslen(seaf_repo_ico_w);
|
||||
if (wlen + 1 > cchMax)
|
||||
return S_FALSE;
|
||||
|
||||
wmemcpy (pwszIconFile, seaf_repo_ico_w, wlen + 1);
|
||||
free (seaf_repo_ico_w);
|
||||
*pdwFlags = ISIOI_ICONFILE;
|
||||
seaf_ext_log ("[ICON] set icon file %s", seaf_repo_ico);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static STDMETHODIMP
|
||||
GetPriority(void *p, int *priority)
|
||||
{
|
||||
/* The priority value can be 0 ~ 100, with 0 be the highest */
|
||||
*priority = 0;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static STDMETHODIMP
|
||||
IsMemberOf(void *p, LPCWSTR path_w, DWORD attr)
|
||||
{
|
||||
HRESULT ret = S_FALSE;
|
||||
char *path = wchar_to_char(path_w);
|
||||
|
||||
if (!path) {
|
||||
seaf_ext_log ("convert to char failed");
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
/* If length of path is shorter than 3, it should be a drive path,
|
||||
* such as C:\ , which should not be a repo folder ; And the
|
||||
* current folder being displayed must be "My Computer". If we
|
||||
* don't return quickly, it will lag the display.
|
||||
*/
|
||||
if (strlen(path) <= 3) {
|
||||
free (path);
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
if (access(path, F_OK) < 0 ||
|
||||
!(GetFileAttributes(path) & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
ret = S_FALSE;
|
||||
|
||||
} else if (is_repo_top_dir(path)) {
|
||||
seaf_ext_log ("[ICON] Set for %s", path);
|
||||
ret = S_OK;
|
||||
|
||||
} else {
|
||||
ret = S_FALSE;
|
||||
}
|
||||
|
||||
free (path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ULONG STDMETHODCALLTYPE
|
||||
add_ref_seaf_icon_overlay(void *p)
|
||||
{
|
||||
SeafIconOverlay *seaf_icon_overlay = p;
|
||||
return ++(seaf_icon_overlay->count);
|
||||
}
|
||||
|
||||
static ULONG STDMETHODCALLTYPE
|
||||
release_seaf_icon_overlay(void *p)
|
||||
{
|
||||
SeafIconOverlay *seaf_icon_overlay = p;
|
||||
--(seaf_icon_overlay->count);
|
||||
if (seaf_icon_overlay->count == 0) {
|
||||
|
||||
free (seaf_icon_overlay);
|
||||
InterlockedDecrement(&object_count);
|
||||
return 0;
|
||||
}
|
||||
return seaf_icon_overlay->count;
|
||||
}
|
||||
|
||||
static STDMETHODIMP
|
||||
query_interface_seaf_icon_overlay (void *p, REFIID iid, LPVOID FAR *pointer)
|
||||
{
|
||||
/* IShellExInit */
|
||||
if (IsEqualIID(iid, &CLSID_seaf_shell_ext) ||
|
||||
IsEqualIID(iid, &IID_IUnknown) ||
|
||||
IsEqualIID(iid, &IID_IShellIconOverlayIdentifier)) {
|
||||
|
||||
*pointer = p;
|
||||
|
||||
} else {
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
add_ref_seaf_icon_overlay(p);
|
||||
|
||||
return NOERROR;
|
||||
}
|
||||
|
||||
/* IShellIconOverlayIdentifier Vtable */
|
||||
struct IShellIconOverlay_Vtbl IShellIconOverlay_Vtbl = {
|
||||
query_interface_seaf_icon_overlay,
|
||||
add_ref_seaf_icon_overlay,
|
||||
release_seaf_icon_overlay,
|
||||
IsMemberOf,
|
||||
GetOverlayInfo,
|
||||
GetPriority
|
||||
};
|
||||
|
||||
SeafIconOverlay *
|
||||
seaf_icon_overlay_new ()
|
||||
{
|
||||
SeafIconOverlay *seaf_icon_overlay;
|
||||
seaf_icon_overlay = (SeafIconOverlay*)malloc(sizeof(SeafIconOverlay));
|
||||
seaf_icon_overlay->virtual_table = &IShellIconOverlay_Vtbl;
|
||||
seaf_icon_overlay->count = 1;
|
||||
|
||||
return seaf_icon_overlay;
|
||||
}
|
||||
|
15
desktop/explorer/seaf-icon.h
Normal file
@ -0,0 +1,15 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
#ifndef SEAF_ICON_H
|
||||
#define SEAF_ICON_H
|
||||
|
||||
typedef struct SeafIconOverlay SeafIconOverlay;
|
||||
|
||||
/* IShellIconOverlayIdentifier */
|
||||
struct SeafIconOverlay {
|
||||
void *virtual_table;
|
||||
unsigned int count;
|
||||
};
|
||||
|
||||
SeafIconOverlay *seaf_icon_overlay_new ();
|
||||
|
||||
#endif /* SEAF_ICON_H */
|
599
desktop/explorer/seaf-menu.c
Normal file
@ -0,0 +1,599 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
#include "platform.h"
|
||||
|
||||
#include <shlobj.h>
|
||||
#include <io.h>
|
||||
|
||||
#include "seaf-dll.h"
|
||||
#include "seaf-utils.h"
|
||||
#include "seaf-ext-log.h"
|
||||
#include "seaf-menu.h"
|
||||
#include "menu-engine.h"
|
||||
|
||||
struct IShellExtInit_vtbl
|
||||
{
|
||||
STDMETHOD(query_interface)(void *, REFIID, PVOID*);
|
||||
STDMETHOD_(ULONG, add_ref)(void *);
|
||||
STDMETHOD_(ULONG, release)(void *);
|
||||
STDMETHOD(initialize)(void *, LPCITEMIDLIST, LPDATAOBJECT, HKEY);
|
||||
};
|
||||
|
||||
struct IContextMenu_vtbl
|
||||
{
|
||||
STDMETHOD(query_interface)(void *, REFIID, PVOID*);
|
||||
STDMETHOD_(ULONG, add_ref)(void *);
|
||||
STDMETHOD_(ULONG, release)(void *);
|
||||
STDMETHOD(query_context_menu)(void *, HMENU, UINT, UINT, UINT, UINT);
|
||||
STDMETHOD(invoke_command)(void *, LPCMINVOKECOMMANDINFO);
|
||||
STDMETHOD(get_command_string)(void *, UINT, UINT, PUINT, LPSTR, UINT);
|
||||
STDMETHOD(HandleMenuMsg)(void *,UINT,WPARAM,LPARAM);
|
||||
STDMETHOD(HandleMenuMsg2)(void *, UINT,WPARAM,LPARAM,LRESULT*);
|
||||
};
|
||||
|
||||
static ULONG STDMETHODCALLTYPE
|
||||
add_ref_seaf_menu(void *p)
|
||||
{
|
||||
SeafMenu *seaf_menu = p;
|
||||
return ++(seaf_menu->count);
|
||||
}
|
||||
|
||||
static ULONG STDMETHODCALLTYPE
|
||||
add_ref_seaf_ishellextinit(void *p)
|
||||
{
|
||||
struct seaf_IShellExtInit *s = p;
|
||||
SeafMenu *seaf_menu = s->seaf_menu;
|
||||
return add_ref_seaf_menu(seaf_menu);
|
||||
}
|
||||
|
||||
static ULONG STDMETHODCALLTYPE
|
||||
release_seaf_menu(void *p)
|
||||
{
|
||||
SeafMenu *seaf_menu = p;
|
||||
--(seaf_menu->count);
|
||||
if (seaf_menu->count == 0) {
|
||||
|
||||
seaf_menu_free (seaf_menu);
|
||||
InterlockedDecrement(&object_count);
|
||||
return 0;
|
||||
}
|
||||
return seaf_menu->count;
|
||||
}
|
||||
|
||||
static ULONG STDMETHODCALLTYPE
|
||||
release_seaf_ishellextinit(void *p)
|
||||
{
|
||||
struct seaf_IShellExtInit *s = p;
|
||||
SeafMenu *seaf_menu = s->seaf_menu;
|
||||
return release_seaf_menu(seaf_menu);
|
||||
}
|
||||
|
||||
static STDMETHODIMP
|
||||
query_interface_seaf_menu (void *p, REFIID iid, LPVOID FAR *pointer)
|
||||
{
|
||||
SeafMenu *seaf_menu = p;
|
||||
/* IShellExInit */
|
||||
if (IsEqualIID(iid, &IID_IShellExtInit) ||
|
||||
IsEqualIID(iid, &CLSID_seaf_shell_ext) ||
|
||||
IsEqualIID(iid, &IID_IUnknown)) {
|
||||
|
||||
*pointer = &seaf_menu->ishellextinit;
|
||||
|
||||
}
|
||||
/* IContextMenu */
|
||||
else if (IsEqualIID(iid, &IID_IContextMenu) ||
|
||||
IsEqualIID(iid, &IID_IContextMenu2) ||
|
||||
IsEqualIID(iid, &IID_IContextMenu3)) {
|
||||
|
||||
*pointer = p;
|
||||
|
||||
} else {
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
add_ref_seaf_menu(p);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static STDMETHODIMP
|
||||
query_interface_seaf_ishellextinit (void *p, REFIID iid, LPVOID FAR *pointer)
|
||||
{
|
||||
struct seaf_IShellExtInit *s = p;
|
||||
SeafMenu *seaf_menu = s->seaf_menu;
|
||||
return query_interface_seaf_menu (seaf_menu, iid, pointer);
|
||||
}
|
||||
|
||||
static STDMETHODIMP
|
||||
initialize_seaf_ishellextinit(void *p, LPCITEMIDLIST folder, LPDATAOBJECT data, HKEY id)
|
||||
{
|
||||
struct seaf_IShellExtInit *s = p;
|
||||
SeafMenu *seaf_menu = s->seaf_menu;
|
||||
|
||||
FORMATETC format = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
STGMEDIUM stg = {TYMED_HGLOBAL};
|
||||
HDROP drop;
|
||||
UINT count;
|
||||
HRESULT result = S_OK;
|
||||
|
||||
/* 'folder' param is not null only when clicking at the foler background;
|
||||
When right click on a file, it's NULL */
|
||||
if (folder)
|
||||
SHGetPathFromIDList(folder, seaf_menu->name);
|
||||
|
||||
/* if 'data' is NULL, then it's a background click, we have set
|
||||
* this_->name to folder's name above, and the Init work is done */
|
||||
if (!data)
|
||||
return S_OK;
|
||||
|
||||
/* 'data' is no null, which means we are operating on a file. the
|
||||
* following lines until the end of the function is used to
|
||||
* extract the filename of the current file. */
|
||||
if (FAILED(data->lpVtbl->GetData(data, &format, &stg)))
|
||||
return E_INVALIDARG;
|
||||
|
||||
drop = (HDROP)GlobalLock(stg.hGlobal);
|
||||
if (!drop)
|
||||
return E_INVALIDARG;
|
||||
|
||||
count = DragQueryFile(drop, 0xFFFFFFFF, NULL, 0);
|
||||
if (count == 0)
|
||||
result = E_INVALIDARG;
|
||||
else if (!DragQueryFile(drop, 0, seaf_menu->name, sizeof(seaf_menu->name)))
|
||||
result = E_INVALIDARG;
|
||||
|
||||
GlobalUnlock(stg.hGlobal);
|
||||
ReleaseStgMedium(&stg);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* These are the functions for handling the context menu.
|
||||
*/
|
||||
static char *get_menu_item_text(SeafMenu *seaf_menu, UINT id);
|
||||
static int handle_menu_item(SeafMenu *seaf_menu, UINT id);
|
||||
|
||||
static bool
|
||||
insert_main_menu (SeafMenu *seaf_menu)
|
||||
{
|
||||
bool status;
|
||||
/* Two menu seperators with seafile menu between them */
|
||||
status = InsertMenu
|
||||
(seaf_menu->main_menu, seaf_menu->index++,
|
||||
MF_BYPOSITION |MF_SEPARATOR, 0, "");
|
||||
|
||||
if (!status)
|
||||
return FALSE;
|
||||
|
||||
char *name = "Seafile";
|
||||
MENUITEMINFO menuiteminfo;
|
||||
|
||||
ZeroMemory(&menuiteminfo, sizeof(menuiteminfo));
|
||||
menuiteminfo.cbSize = sizeof(menuiteminfo);
|
||||
menuiteminfo.fMask = MIIM_FTYPE | MIIM_SUBMENU | MIIM_BITMAP | MIIM_STRING | MIIM_ID;
|
||||
menuiteminfo.fType = MFT_STRING;
|
||||
menuiteminfo.dwTypeData = name;
|
||||
menuiteminfo.cch = strlen(name);
|
||||
menuiteminfo.hbmpItem = HBMMENU_CALLBACK;
|
||||
menuiteminfo.hSubMenu = seaf_menu->sub_menu;
|
||||
menuiteminfo.wID = seaf_menu->first;
|
||||
|
||||
status = InsertMenuItem
|
||||
(seaf_menu->main_menu, /* menu */
|
||||
seaf_menu->index++, /* position */
|
||||
TRUE, /* by position */
|
||||
&menuiteminfo);
|
||||
if (!status)
|
||||
return FALSE;
|
||||
|
||||
status = InsertMenu (seaf_menu->main_menu, seaf_menu->index++,
|
||||
MF_BYPOSITION |MF_SEPARATOR, 0, "");
|
||||
|
||||
if (!status)
|
||||
return FALSE;
|
||||
|
||||
/* Set menu styles of submenu */
|
||||
MENUINFO MenuInfo;
|
||||
ZeroMemory(&MenuInfo, sizeof(MenuInfo));
|
||||
MenuInfo.cbSize = sizeof(MenuInfo);
|
||||
MenuInfo.fMask = MIM_STYLE | MIM_APPLYTOSUBMENUS;
|
||||
MenuInfo.dwStyle = MNS_CHECKORBMP;
|
||||
|
||||
SetMenuInfo(seaf_menu->main_menu, &MenuInfo);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static bool
|
||||
should_ignore (SeafMenu *seaf_menu) {
|
||||
/* Show no menu for drive root, such as C: D: */
|
||||
if (strlen(seaf_menu->name) <= 3) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
char drive[4];
|
||||
memcpy (drive, seaf_menu->name, 3);
|
||||
drive[3] = '\0';
|
||||
|
||||
/* Ignore flash disk, network mounted drive, etc. */
|
||||
if (GetDriveType(drive) != DRIVE_FIXED) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static STDMETHODIMP
|
||||
query_context_menu(void *p, HMENU menu, UINT index,
|
||||
UINT first_command, UINT last_command, UINT flags)
|
||||
{
|
||||
/* do nothing when user is double clicking */
|
||||
if (flags & CMF_DEFAULTONLY)
|
||||
return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
|
||||
|
||||
SeafMenu *seaf_menu = p;
|
||||
|
||||
if (should_ignore(seaf_menu)) {
|
||||
return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
|
||||
}
|
||||
|
||||
seaf_menu->main_menu = menu;
|
||||
seaf_menu->first = first_command;
|
||||
seaf_menu->last = last_command;
|
||||
seaf_menu->index = 0;
|
||||
|
||||
build_seafile_menu (seaf_menu);
|
||||
|
||||
if (seaf_menu->next_active_item > 0) {
|
||||
if (!insert_main_menu (seaf_menu))
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL,
|
||||
3 + seaf_menu->next_active_item);
|
||||
}
|
||||
|
||||
static STDMETHODIMP
|
||||
invoke_command(void *p, LPCMINVOKECOMMANDINFO info)
|
||||
{
|
||||
SeafMenu *seaf_menu = p;
|
||||
|
||||
UINT id = LOWORD(info->lpVerb);
|
||||
|
||||
if (HIWORD(info->lpVerb))
|
||||
return E_INVALIDARG;
|
||||
|
||||
if (id == 0)
|
||||
return S_OK;
|
||||
|
||||
id--;
|
||||
handle_menu_item(seaf_menu, id);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static STDMETHODIMP
|
||||
get_command_string(void *p, UINT id, UINT flags, UINT *reserved,
|
||||
LPSTR name, UINT size)
|
||||
{
|
||||
if (!(flags & GCS_HELPTEXT))
|
||||
return E_INVALIDARG;
|
||||
|
||||
SeafMenu *seaf_menu = p;
|
||||
|
||||
char *text = NULL;
|
||||
|
||||
if (id == 0) {
|
||||
text = "Seafile";
|
||||
} else {
|
||||
id--;
|
||||
text = get_menu_item_text(seaf_menu, id);
|
||||
}
|
||||
|
||||
if (!text)
|
||||
return E_INVALIDARG;
|
||||
|
||||
if (flags & GCS_UNICODE) {
|
||||
wchar_t *wtext = char_to_wchar (text);
|
||||
if (!wtext)
|
||||
return S_FALSE;
|
||||
lstrcpynW ((LPWSTR)name, wtext, size);
|
||||
free (wtext);
|
||||
} else
|
||||
lstrcpynA(name, text, size);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
extern char *seaf_ext_ico;
|
||||
static HICON menu_icons_cache[SEAF_OP_MAX];
|
||||
|
||||
static HICON
|
||||
load_menu_item_icon (SeafMenu *seaf_menu, UINT itemID)
|
||||
{
|
||||
UINT index = 0;
|
||||
char *icon_file = NULL;
|
||||
|
||||
if (itemID == seaf_menu->first) {
|
||||
/* main menu bar item */
|
||||
index = 0;
|
||||
icon_file = seaf_ext_ico;
|
||||
} else {
|
||||
/* sub menu bar item */
|
||||
itemID -= seaf_menu->first + 1;
|
||||
if (itemID > seaf_menu->next_active_item) {
|
||||
seaf_ext_log ("itemID too large, %u(%d)",
|
||||
itemID, seaf_menu->next_active_item);
|
||||
return NULL;
|
||||
}
|
||||
struct menu_item *mi = &seaf_menu->active_menu[itemID];
|
||||
index = mi->op;
|
||||
icon_file = mi->icon;
|
||||
}
|
||||
|
||||
if (menu_icons_cache[index]) {
|
||||
/* Icon for this menu item has been cached. */
|
||||
return menu_icons_cache[index];
|
||||
} else {
|
||||
if (!icon_file)
|
||||
return NULL;
|
||||
if (access(icon_file, F_OK) < 0) {
|
||||
seaf_ext_log ("icon file doesn't found, %s", icon_file);
|
||||
return NULL;
|
||||
}
|
||||
HICON hIcon = LoadImage
|
||||
(NULL,
|
||||
(LPCTSTR)icon_file,
|
||||
IMAGE_ICON,
|
||||
16,16,
|
||||
LR_LOADFROMFILE);
|
||||
if (!hIcon) {
|
||||
seaf_ext_log ("failed to load %s", icon_file);
|
||||
return NULL;
|
||||
}
|
||||
/* Cache the loaded icon. */
|
||||
menu_icons_cache[index] = hIcon;
|
||||
return hIcon;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Handles the WM_MEASUREITEM and WM_DRAWITEM messages, so we can draw icons
|
||||
* for menu items
|
||||
*/
|
||||
static STDMETHODIMP
|
||||
HandleMenuMsg2(void *p, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult)
|
||||
{
|
||||
SeafMenu *seaf_menu = p;
|
||||
/* we use a 16*16 icon for each menu item */
|
||||
int icon_size = 16;
|
||||
|
||||
if (uMsg == WM_INITMENUPOPUP) {
|
||||
|
||||
} else if (uMsg == WM_MEASUREITEM) {
|
||||
MEASUREITEMSTRUCT* lpmis = (MEASUREITEMSTRUCT*)lParam;
|
||||
if (!lpmis)
|
||||
return S_OK;
|
||||
|
||||
lpmis->itemWidth = icon_size;
|
||||
lpmis->itemHeight = icon_size;
|
||||
|
||||
} else if (uMsg == WM_DRAWITEM) {
|
||||
DRAWITEMSTRUCT* lpdis = (DRAWITEMSTRUCT*)lParam;
|
||||
if (!lpdis || (lpdis->CtlType != ODT_MENU))
|
||||
return S_OK; // not for a menu
|
||||
|
||||
HICON hIcon = load_menu_item_icon(seaf_menu, lpdis->itemID);
|
||||
if (hIcon == NULL)
|
||||
return S_OK;
|
||||
|
||||
RECT *rect = &lpdis->rcItem;
|
||||
int left = rect->left;
|
||||
int right = rect->right;
|
||||
int top = rect->top;
|
||||
int bottom = rect->bottom;
|
||||
|
||||
DrawIconEx(lpdis->hDC,
|
||||
left + (right - left - icon_size) / 2,
|
||||
bottom + (top - bottom - icon_size)/2,
|
||||
hIcon, icon_size, icon_size,
|
||||
0, NULL, DI_NORMAL);
|
||||
} else {
|
||||
seaf_ext_log ("[MSG] UNKNOWN MSG");
|
||||
return S_FALSE;
|
||||
}
|
||||
if (plResult)
|
||||
*plResult = TRUE;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
||||
static STDMETHODIMP
|
||||
HandleMenuMsg(void *p, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
return HandleMenuMsg2(p, uMsg, wParam, lParam, NULL);
|
||||
}
|
||||
|
||||
|
||||
/* Menu operations */
|
||||
|
||||
/* Clears the list every time a new menu is displayed */
|
||||
void reset_active_menu(SeafMenu *seaf_menu)
|
||||
{
|
||||
if (seaf_menu->active_menu)
|
||||
free(seaf_menu->active_menu);
|
||||
seaf_menu->active_menu = NULL;
|
||||
seaf_menu->next_active_item = 0;
|
||||
seaf_menu->selection = SEAF_MI_ALWAYS;
|
||||
}
|
||||
|
||||
/* Append all activited menu items into a list */
|
||||
void
|
||||
append_active_menu(SeafMenu *seaf_menu, const struct menu_item *item)
|
||||
{
|
||||
seaf_menu->active_menu = realloc
|
||||
(seaf_menu->active_menu,
|
||||
(seaf_menu->next_active_item + 1) * sizeof(struct menu_item));
|
||||
|
||||
seaf_menu->active_menu[seaf_menu->next_active_item] = *item;
|
||||
seaf_menu->next_active_item++;
|
||||
}
|
||||
|
||||
static char *
|
||||
get_menu_item_text(SeafMenu *seaf_menu, UINT id)
|
||||
{
|
||||
if (id > (UINT)seaf_menu->next_active_item) {
|
||||
seaf_ext_log ("invalid menu id %u", id);
|
||||
return NULL;
|
||||
} else {
|
||||
return seaf_menu->active_menu[id].helptext;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
handle_menu_item(SeafMenu *seaf_menu, UINT id)
|
||||
{
|
||||
if (id > seaf_menu->next_active_item) {
|
||||
seaf_ext_log ("invalid menu id %u", id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
dispatch_menu_command (seaf_menu, &seaf_menu->active_menu[id].op);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Fill a MENUITEMINFO struct with info of a seafile menu item */
|
||||
static void
|
||||
menu_item_to_info (SeafMenu *seaf_menu, const struct menu_item *mi,
|
||||
MENUITEMINFO *minfo)
|
||||
{
|
||||
minfo->cbSize = sizeof(MENUITEMINFO);
|
||||
minfo->fMask = MIIM_FTYPE | MIIM_BITMAP | MIIM_STRING | MIIM_ID;
|
||||
minfo->fType = MFT_STRING;
|
||||
minfo->dwTypeData = mi->string;
|
||||
minfo->cch = strlen(mi->string);
|
||||
minfo->hbmpItem = HBMMENU_CALLBACK;
|
||||
|
||||
/* menu->first is used by main menu item "seafile" */
|
||||
minfo->wID = seaf_menu->first + 1 + seaf_menu->next_active_item;
|
||||
}
|
||||
|
||||
static void
|
||||
set_menu_item_checked (SeafMenu *seaf_menu, const struct menu_item *mi,
|
||||
MENUITEMINFO *minfo)
|
||||
{
|
||||
minfo->fMask = MIIM_FTYPE | MIIM_STRING | MIIM_ID | MIIM_STATE | MIIM_CHECKMARKS;
|
||||
minfo->fType = MFT_STRING | MFT_RADIOCHECK;
|
||||
minfo->hbmpChecked = NULL;
|
||||
minfo->hbmpUnchecked = NULL;
|
||||
|
||||
if (mi->op == SEAF_TURN_ON_AUTO) {
|
||||
if (seaf_menu->selection & SEAF_MI_TURN_ON_AUTO)
|
||||
minfo->fState = MFS_UNCHECKED;
|
||||
else
|
||||
minfo->fState = MFS_CHECKED;
|
||||
} else { /* mi->op == TURN_OFF_AUTO */
|
||||
if (seaf_menu->selection & SEAF_MI_TURN_ON_AUTO)
|
||||
minfo->fState = MFS_CHECKED;
|
||||
else
|
||||
minfo->fState = MFS_UNCHECKED;
|
||||
}
|
||||
}
|
||||
|
||||
/* Insert a new menu item according to its type.
|
||||
|
||||
When the menu item is a menu seperator, don't add it directly because we
|
||||
don't know whethter it would be the last menu item, in which case it would
|
||||
be redundant. We set the `add_sep' flag, and every time we add a new menu
|
||||
item, we check this flag to decide whether a menu seperator should be added
|
||||
before this current menu item.
|
||||
*/
|
||||
bool
|
||||
build_menu_item(SeafMenu *seaf_menu, const struct menu_item *mi)
|
||||
{
|
||||
if (seaf_menu->last < seaf_menu->first + seaf_menu->next_active_item)
|
||||
return FALSE;
|
||||
|
||||
/* Check whether a menu seperator is pending */
|
||||
if (mi->op != SEAF_NONE && seaf_menu->add_sep) {
|
||||
InsertMenu (seaf_menu->sub_menu, seaf_menu->index++,
|
||||
MF_SEPARATOR | MF_BYPOSITION, 0, "");
|
||||
seaf_menu->add_sep = FALSE;
|
||||
}
|
||||
|
||||
if (mi->op == SEAF_NONE ) {
|
||||
seaf_menu->add_sep = TRUE;
|
||||
} else {
|
||||
MENUITEMINFO menuiteminfo = {0};
|
||||
menu_item_to_info (seaf_menu, mi, &menuiteminfo);
|
||||
|
||||
if (mi->op == SEAF_TURN_ON_AUTO || mi->op == SEAF_TURN_OFF_AUTO) {
|
||||
set_menu_item_checked (seaf_menu, mi, &menuiteminfo);
|
||||
}
|
||||
|
||||
InsertMenuItem (seaf_menu->sub_menu, /* menu */
|
||||
seaf_menu->index++, /* position */
|
||||
TRUE, /* by position */
|
||||
&menuiteminfo);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/* The vtable of IShellExInit */
|
||||
struct IShellExtInit_vtbl IShellExtInit_vtbl = {
|
||||
query_interface_seaf_ishellextinit,
|
||||
add_ref_seaf_ishellextinit,
|
||||
release_seaf_ishellextinit,
|
||||
initialize_seaf_ishellextinit
|
||||
};
|
||||
|
||||
/**
|
||||
* The vtable of IContextMenu
|
||||
*
|
||||
* Note: Pay attention to the order of the last three methods. IN MSDN docs,
|
||||
* they are listed in alphabetic order, which is not their order in the
|
||||
* Vtable! Google `IContextMenu Vtable order', you can get the right
|
||||
* information
|
||||
*/
|
||||
struct IContextMenu_vtbl IContextMenu_vtbl = {
|
||||
query_interface_seaf_menu,
|
||||
add_ref_seaf_menu,
|
||||
release_seaf_menu,
|
||||
query_context_menu,
|
||||
invoke_command,
|
||||
get_command_string,
|
||||
HandleMenuMsg,
|
||||
HandleMenuMsg2
|
||||
};
|
||||
|
||||
SeafMenu *seaf_menu_new ()
|
||||
{
|
||||
SeafMenu *seaf_menu;
|
||||
|
||||
seaf_menu = (SeafMenu *)malloc (sizeof(SeafMenu));
|
||||
ZeroMemory (seaf_menu, sizeof(SeafMenu));
|
||||
seaf_menu->virtual_table = &IContextMenu_vtbl;
|
||||
seaf_menu->sub_menu = CreateMenu();
|
||||
|
||||
seaf_menu->ishellextinit.virtual_table = &IShellExtInit_vtbl;
|
||||
seaf_menu->ishellextinit.seaf_menu = seaf_menu;
|
||||
|
||||
seaf_menu->count = 1;
|
||||
|
||||
return seaf_menu;
|
||||
}
|
||||
|
||||
void seaf_menu_free (SeafMenu *seaf_menu)
|
||||
{
|
||||
if (!seaf_menu)
|
||||
return;
|
||||
|
||||
if (seaf_menu->active_menu) {
|
||||
free (seaf_menu->active_menu);
|
||||
}
|
||||
|
||||
free (seaf_menu);
|
||||
|
||||
}
|
37
desktop/explorer/seaf-menu.h
Normal file
@ -0,0 +1,37 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
#ifndef SEAF_MENU_H
|
||||
#define SEAF_MENU_H
|
||||
|
||||
typedef struct SeafMenu SeafMenu;
|
||||
|
||||
/* IShellExInit interface */
|
||||
struct seaf_IShellExtInit {
|
||||
void *virtual_table;
|
||||
SeafMenu *seaf_menu;
|
||||
};
|
||||
|
||||
/* IContextMenu interface */
|
||||
struct SeafMenu {
|
||||
void *virtual_table;
|
||||
struct seaf_IShellExtInit ishellextinit;
|
||||
|
||||
HMENU main_menu; /* the main menu */
|
||||
HMENU sub_menu; /* the popup seafile submenu */
|
||||
UINT index;
|
||||
UINT first;
|
||||
UINT last;
|
||||
struct menu_item *active_menu;
|
||||
unsigned int next_active_item;
|
||||
bool add_sep;
|
||||
|
||||
unsigned int count;
|
||||
unsigned int selection;
|
||||
char name[MAX_PATH]; /* the file/dir current clicked on */
|
||||
char repo_id[37]; /* set if in a repo dir */
|
||||
char repo_wt[MAX_PATH]; /* repo top wt, set if in a repo dir */
|
||||
};
|
||||
|
||||
SeafMenu *seaf_menu_new ();
|
||||
void seaf_menu_free (SeafMenu *seaf_menu);
|
||||
|
||||
#endif /* SEAF_MENU_H */
|
9
desktop/explorer/seafile_shell_ext.def
Normal file
@ -0,0 +1,9 @@
|
||||
LIBRARY seafile_shell_ext
|
||||
EXPORTS
|
||||
DllMain
|
||||
DllRegisterServer PRIVATE
|
||||
DllUnregisterServer PRIVATE
|
||||
DllCanUnloadNow PRIVATE
|
||||
DllGetClassObject PRIVATE
|
||||
DllInstall PRIVATE
|
||||
TerminateSeafile
|
68
desktop/nautilus/Makefile.in
Normal file
@ -0,0 +1,68 @@
|
||||
CC = gcc
|
||||
|
||||
# Default make goal is for nautilus 3; use `make v2' to compile for nautilus 2
|
||||
ifeq (${MAKECMDGOALS}, v2)
|
||||
# compile for nautilus 2
|
||||
TARGET = libseafile_nautilus2_ext.so
|
||||
NAUTILUS_VERSION = 2
|
||||
NAUTILUS_EXT_DIR = $(DESTDIR)/usr/lib/nautilus/extensions-2.0
|
||||
else
|
||||
# compile for nautilus 3
|
||||
TARGET = libseafile_nautilus3_ext.so
|
||||
NAUTILUS_VERSION = 3
|
||||
NAUTILUS_EXT_DIR = $(DESTDIR)/usr/lib/nautilus/extensions-3.0
|
||||
endif
|
||||
|
||||
SEAF_EXT_UI_DIR = ${NAUTILUS_EXT_DIR}/seafile/
|
||||
|
||||
CFLAGS = @LIBNAUTILUS_EXTENSION_CFLAGS@ \
|
||||
-I../common -fPIC -Wall -Werror -g -O \
|
||||
-DSEAF_EXT_UI_DIR=\"${SEAF_EXT_UI_DIR}\" \
|
||||
-DNAUTILUS_VERSION=${NAUTILUS_VERSION}
|
||||
|
||||
LDFLAGS = -g -shared -Wl,-soname,${TARGET} \
|
||||
@LIBNAUTILUS_EXTENSION_LIBS@
|
||||
|
||||
MODULES = platform.c seaf-ext.c seaf-menu.c \
|
||||
seaf-dlgs.c \
|
||||
../common/strbuf.c ../common/seaf-ext-log.c \
|
||||
../common/seaf-utils.c ../common/menu-engine.c
|
||||
|
||||
OBJECTS = ${MODULES:%.c=%.o}
|
||||
|
||||
ICON_DIR = ../common/icons
|
||||
ICON_EXT = .ico
|
||||
|
||||
UIFILES = ${ICON_DIR}/seaf_ext*${ICON_EXT}
|
||||
|
||||
all: ${TARGET}
|
||||
v3: ${TARGET}
|
||||
|
||||
.deps: $(MODULES)
|
||||
$(CC) $(CFLAGS) -MM $^ > $@
|
||||
|
||||
include .deps
|
||||
|
||||
%.o : %.c
|
||||
$(CC) $(CFLAGS) $< -c -o $@
|
||||
|
||||
${TARGET}: ${OBJECTS} .deps
|
||||
${CC} ${LDFLAGS} ${OBJECTS} -o $@
|
||||
|
||||
|
||||
install: ${TARGET}
|
||||
mkdir -p ${SEAF_EXT_UI_DIR}
|
||||
cp -f ${TARGET} ${NAUTILUS_EXT_DIR}
|
||||
cp -f ${UIFILES} ${SEAF_EXT_UI_DIR}
|
||||
|
||||
uninstall:
|
||||
rm -f ${NAUTILUS_EXT_DIR}/${TARGET}
|
||||
rm -rf ${SEAF_EXT_UI_DIR}
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so ../common/*.o .deps
|
||||
|
||||
distclean:
|
||||
rm -f Makefile
|
||||
|
||||
.PHONY: all clean distclean install uninstall
|
414
desktop/nautilus/platform.c
Normal file
@ -0,0 +1,414 @@
|
||||
#include "platform.h"
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/select.h>
|
||||
#include <pthread.h>
|
||||
#include <dirent.h>
|
||||
#include <libnautilus-extension/nautilus-menu-provider.h>
|
||||
|
||||
#include "seaf-utils.h"
|
||||
#include "seaf-dlgs.h"
|
||||
#include "seaf-ext-log.h"
|
||||
#include "menu-engine.h"
|
||||
|
||||
|
||||
const char *
|
||||
get_home_dir()
|
||||
{
|
||||
static char *home;
|
||||
if (!home) {
|
||||
home = strdup(getenv("HOME"));
|
||||
regulate_path(home);
|
||||
}
|
||||
|
||||
return home;
|
||||
}
|
||||
|
||||
bool
|
||||
seaf_mutex_init (void *vmutex)
|
||||
{
|
||||
pthread_mutex_t *p_mutex = vmutex;
|
||||
|
||||
int ret = pthread_mutex_init(p_mutex, NULL);
|
||||
if (ret != 0) {
|
||||
seaf_ext_log ("failed to init pthread mutex, "
|
||||
"error code %d", ret);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
inline bool
|
||||
seaf_mutex_acquire(void *vmutex, bool blocking)
|
||||
{
|
||||
pthread_mutex_t *p_mutex = vmutex;
|
||||
|
||||
int ret = 0;
|
||||
|
||||
if (blocking)
|
||||
ret = pthread_mutex_lock(p_mutex);
|
||||
else
|
||||
ret = pthread_mutex_trylock(p_mutex);
|
||||
|
||||
return (ret == 0);
|
||||
}
|
||||
|
||||
inline bool
|
||||
seaf_mutex_release(void *vmutex)
|
||||
{
|
||||
pthread_mutex_t *p_mutex = vmutex;
|
||||
|
||||
int ret = pthread_mutex_unlock(p_mutex);
|
||||
|
||||
return (ret == 0);
|
||||
}
|
||||
|
||||
/* Read "n" bytes from a descriptor. */
|
||||
static ssize_t
|
||||
readn(int fd, void *vptr, size_t n)
|
||||
{
|
||||
size_t nleft;
|
||||
ssize_t nread;
|
||||
char *ptr;
|
||||
|
||||
ptr = vptr;
|
||||
nleft = n;
|
||||
while (nleft > 0) {
|
||||
if ( (nread = read(fd, ptr, nleft)) < 0) {
|
||||
if (errno == EINTR)
|
||||
nread = 0; /* and call read() again */
|
||||
else
|
||||
return(-1);
|
||||
} else if (nread == 0)
|
||||
break; /* EOF */
|
||||
|
||||
nleft -= nread;
|
||||
ptr += nread;
|
||||
}
|
||||
return(n - nleft); /* return >= 0 */
|
||||
}
|
||||
|
||||
/* Write "n" bytes to a descriptor. */
|
||||
static ssize_t
|
||||
writen(int fd, const void *vptr, size_t n)
|
||||
{
|
||||
size_t nleft;
|
||||
ssize_t nwritten;
|
||||
const char *ptr;
|
||||
|
||||
ptr = vptr;
|
||||
nleft = n;
|
||||
while (nleft > 0) {
|
||||
if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
|
||||
if (nwritten < 0 && errno == EINTR)
|
||||
nwritten = 0; /* and call write() again */
|
||||
else
|
||||
return(-1); /* error */
|
||||
}
|
||||
|
||||
nleft -= nwritten;
|
||||
ptr += nwritten;
|
||||
}
|
||||
return(n);
|
||||
}
|
||||
|
||||
/* Use a wrapper in case we need to extend it in the future */
|
||||
static void *
|
||||
job_thread_wrapper (void *vdata)
|
||||
{
|
||||
SeafExtJob *job = vdata;
|
||||
|
||||
int status = job->thread_func(job->data);
|
||||
|
||||
free (job);
|
||||
|
||||
return (void *)(long)status;
|
||||
}
|
||||
|
||||
int
|
||||
seaf_ext_start_thread(SeafThreadFunc thread_func, void *data, void *p_handle)
|
||||
{
|
||||
SeafExtJob *job = g_new0(SeafExtJob, 1);
|
||||
job->thread_func = thread_func;
|
||||
job->data = data;
|
||||
|
||||
pthread_t pt;
|
||||
int ret = pthread_create
|
||||
(&pt,
|
||||
NULL, /* pthread_attr_t */
|
||||
job_thread_wrapper, /* start routine */
|
||||
job); /* start routine data */
|
||||
|
||||
if (ret != 0) {
|
||||
seaf_ext_log ("failed to create pthread, error code %d", ret);
|
||||
g_free(job);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (p_handle)
|
||||
*(pthread_t *)p_handle = pt;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* read the link of /proc/123/exe and compare with `process_name' */
|
||||
static int
|
||||
find_process_in_dirent(struct dirent *dir, char *process_name)
|
||||
{
|
||||
char path[512];
|
||||
/* fisrst construct a path like /proc/123/exe */
|
||||
if (sprintf (path, "/proc/%s/exe", dir->d_name) < 0) {
|
||||
return -1;
|
||||
}
|
||||
char buf[MAX_PATH];
|
||||
/* get the full path of exe */
|
||||
ssize_t l = readlink(path, buf, MAX_PATH);
|
||||
|
||||
if (l<0)
|
||||
return -1;
|
||||
buf[l] = '\0';
|
||||
/* get the base name of exe */
|
||||
char *base = g_path_get_basename(buf);
|
||||
int ret = strcmp(base, process_name);
|
||||
g_free(base);
|
||||
if (ret == 0)
|
||||
return atoi(dir->d_name);
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* read the /proc fs to determine whether some process is running */
|
||||
bool
|
||||
process_is_running (const char *process_name)
|
||||
{
|
||||
DIR *proc_dir = opendir("/proc");
|
||||
if (!proc_dir) {
|
||||
seaf_ext_log("Error: failed to open /proc dir: %s",
|
||||
strerror(errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
struct dirent *subdir = NULL;
|
||||
while ((subdir = readdir(proc_dir))) {
|
||||
char first = subdir->d_name[0];
|
||||
/* /proc/[1-9][0-9]* */
|
||||
if (first > '9' || first < '1')
|
||||
continue;
|
||||
int pid = find_process_in_dirent(subdir, (char*)process_name);
|
||||
if (pid > 0) {
|
||||
closedir(proc_dir);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
closedir(proc_dir);
|
||||
/* seaf_ext_log("No %s running", process_name); */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bool
|
||||
is_main_thread()
|
||||
{
|
||||
GMainContext *main_context = g_main_context_default();
|
||||
|
||||
if (g_main_context_is_owner(main_context)) {
|
||||
/* seaf_ext_log ("You are the owner of main context\n"); */
|
||||
return TRUE;
|
||||
} else {
|
||||
/* seaf_ext_log ("You are not the owner of main context\n"); */
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
char *get_folder_path (const char *path)
|
||||
{
|
||||
if (!path)
|
||||
return NULL;
|
||||
|
||||
if (g_file_test(path, G_FILE_TEST_IS_DIR))
|
||||
return g_strdup(path);
|
||||
|
||||
return g_path_get_dirname(path);
|
||||
}
|
||||
|
||||
char *
|
||||
get_base_name(const char *path)
|
||||
{
|
||||
if (!path)
|
||||
return NULL;
|
||||
|
||||
return g_path_get_basename (path);
|
||||
}
|
||||
|
||||
static char browser_path[MAX_PATH] = {'\0'};
|
||||
|
||||
static void find_browser()
|
||||
{
|
||||
static char *browser_table[] =
|
||||
{"x-www-browser", "firefox", "chromium-browser", NULL};
|
||||
|
||||
char *path = NULL;
|
||||
int i = 0;
|
||||
while (browser_table[i] && !path) {
|
||||
path = g_find_program_in_path(browser_table[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (path) {
|
||||
memcpy (browser_path, path, strlen(path) + 1);
|
||||
seaf_ext_log("find web browser, %s", path);
|
||||
g_free (path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void open_browser(char *url)
|
||||
{
|
||||
if (browser_path[0] == '\0')
|
||||
find_browser();
|
||||
|
||||
/* still not found */
|
||||
if (browser_path[0] == '\0') {
|
||||
|
||||
GString *gstr = g_string_new(MSG_BROWSER_NOT_FOUND);
|
||||
g_string_append_printf(gstr, MSG_OPEN_URL_YOURSELF ":\n%s", url);
|
||||
|
||||
msgbox (gstr->str);
|
||||
g_string_free (gstr, TRUE);
|
||||
return;
|
||||
}
|
||||
|
||||
seaf_ext_log("URL: %s", url);
|
||||
|
||||
GString *gs = g_string_new (NULL);
|
||||
g_string_append_printf (gs, "%s %s", browser_path, url);
|
||||
if (spawn_process (gs->str, NULL) < 0) {
|
||||
msgbox_warning ("Failed to open browser");
|
||||
}
|
||||
g_string_free (gs, TRUE);
|
||||
}
|
||||
|
||||
|
||||
char *seaf_uri_escape(const char *path)
|
||||
{
|
||||
char *uri = g_uri_escape_string(path, NULL, TRUE);
|
||||
return uri;
|
||||
}
|
||||
|
||||
static bool ext_pipe_connected = FALSE;
|
||||
static int ext_pipe = -1;
|
||||
|
||||
inline bool
|
||||
ext_pipe_is_connected ()
|
||||
{
|
||||
return ext_pipe_connected;
|
||||
}
|
||||
|
||||
#define SEAF_EXT_UNIX_SOCKET "/tmp/seafile-ext.socket"
|
||||
|
||||
#define PIPE_WRITE_WAIT_TIME 1
|
||||
#define PIPE_READ_WAIT_TIME 1
|
||||
|
||||
int
|
||||
connect_ext_pipe ()
|
||||
{
|
||||
ext_pipe = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (ext_pipe < 0) {
|
||||
seaf_ext_log ("failed to create ext pipe : %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_un saddr;
|
||||
saddr.sun_family = AF_UNIX;
|
||||
const char *un_path = SEAF_EXT_UNIX_SOCKET;
|
||||
memcpy(saddr.sun_path, un_path, strlen(un_path) + 1);
|
||||
if (connect(ext_pipe, (struct sockaddr *)&saddr, (socklen_t)sizeof(saddr)) < 0) {
|
||||
/* seaf_ext_log ("Failed to connect : %s\n", strerror(errno)); */
|
||||
ext_pipe_connected = FALSE;
|
||||
return -1;
|
||||
}
|
||||
ext_pipe_connected = TRUE;
|
||||
seaf_ext_log ("ext pipe now connected");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spawn_process (char *cmdline_in, char *working_directory)
|
||||
{
|
||||
GError *error = NULL;
|
||||
gboolean result;
|
||||
|
||||
result = g_spawn_command_line_async((const char*)cmdline_in, &error);
|
||||
|
||||
if (!result) {
|
||||
seaf_ext_log ("Failed to spawn [%s] : %s", error->message);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
send_ext_pipe_request_wrapper (const char *request)
|
||||
{
|
||||
uint32_t len = strlen(request) + 1;
|
||||
if (writen (ext_pipe, &len, sizeof(len)) != sizeof(len)) {
|
||||
seaf_ext_log ("[pipe write] Failed to write: %s", strerror(errno));
|
||||
goto failed;
|
||||
}
|
||||
if (writen (ext_pipe, request, len) != len) {
|
||||
seaf_ext_log ("[pipe write] Failed to write: %s", strerror(errno));
|
||||
goto failed;
|
||||
}
|
||||
|
||||
return 0;
|
||||
failed:
|
||||
ext_pipe_connected = FALSE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
char *
|
||||
read_ext_pipe_response ()
|
||||
{
|
||||
char *buf = NULL;
|
||||
uint32_t len = 0;
|
||||
fd_set read_fds;
|
||||
FD_ZERO (&read_fds);
|
||||
FD_SET(ext_pipe, &read_fds);
|
||||
struct timeval tv;
|
||||
tv.tv_sec = PIPE_READ_WAIT_TIME;
|
||||
tv.tv_usec = 0;
|
||||
int result = select(ext_pipe + 1, &read_fds, NULL, NULL, &tv);
|
||||
if (result < 0) {
|
||||
seaf_ext_log ("Failed to select: %s", strerror(errno));
|
||||
goto failed;
|
||||
} else if (result == 0) {
|
||||
seaf_ext_log ("Failed to select: timeout");
|
||||
goto failed;
|
||||
} else if (!FD_ISSET(ext_pipe, &read_fds)) {
|
||||
seaf_ext_log ("FD_ISSET() failes!");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (readn (ext_pipe, &len, sizeof(len)) != sizeof(len)
|
||||
|| len <= 0) {
|
||||
seaf_ext_log ("Failed to read: %s", strerror(errno));
|
||||
goto failed;
|
||||
}
|
||||
|
||||
buf = (char *)g_malloc (len);
|
||||
|
||||
if (readn (ext_pipe, buf, len) != len) {
|
||||
seaf_ext_log ("Failed to read: %s", strerror(errno));
|
||||
goto failed;
|
||||
}
|
||||
return buf;
|
||||
|
||||
failed:
|
||||
ext_pipe_connected = FALSE;
|
||||
if (buf)
|
||||
g_free (buf);
|
||||
return NULL;
|
||||
}
|
||||
|
194
desktop/nautilus/seaf-dlgs.c
Normal file
@ -0,0 +1,194 @@
|
||||
#include "platform.h"
|
||||
|
||||
#include "seaf-dlgs.h"
|
||||
#include "seaf-ext-log.h"
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define SEAF_PRIORITY_IDLE ((G_PRIORITY_DEFAULT_IDLE + G_PRIORITY_HIGH_IDLE) /2 )
|
||||
|
||||
typedef struct SeafExtMsg SeafExtMsg;
|
||||
typedef struct SeafExtQuestion SeafExtQuestion;
|
||||
|
||||
struct SeafExtMsg {
|
||||
char *message;
|
||||
GtkMessageType type;
|
||||
void *user_data;
|
||||
};
|
||||
|
||||
struct SeafExtQuestion {
|
||||
char *question;
|
||||
void *user_data;
|
||||
bool yes_no;
|
||||
pthread_cond_t cond;
|
||||
pthread_mutex_t mutex;
|
||||
};
|
||||
|
||||
|
||||
SeafExtMsg *
|
||||
seaf_ext_msg_new(char *msg_in, GtkMessageType type, void *user_data)
|
||||
{
|
||||
if (!msg_in)
|
||||
return NULL;
|
||||
|
||||
SeafExtMsg *msg = g_new0(SeafExtMsg, 1);
|
||||
|
||||
msg->message = g_strdup(msg_in);
|
||||
msg->type = type;
|
||||
msg->user_data = user_data;
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
SeafExtQuestion *
|
||||
seaf_ext_question_new (char *question_in, void *user_data)
|
||||
{
|
||||
if (!question_in)
|
||||
return NULL;
|
||||
|
||||
SeafExtQuestion *q = g_new0(SeafExtQuestion, 1);
|
||||
|
||||
q->question = g_strdup(question_in);
|
||||
q->user_data = user_data;
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
|
||||
static bool msgbox_wrapper(SeafExtMsg *msg)
|
||||
{
|
||||
GtkWidget *dialog = gtk_message_dialog_new
|
||||
((GtkWindow *)msg->user_data,
|
||||
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
msg->type,
|
||||
GTK_BUTTONS_OK,
|
||||
"%s",
|
||||
msg->message);
|
||||
|
||||
if (dialog) {
|
||||
gtk_window_set_title(GTK_WINDOW(dialog), "Seafile");
|
||||
gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
|
||||
g_free(msg);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void msgbox_full(char *msg_in, GtkMessageType type, void *user_data)
|
||||
{
|
||||
SeafExtMsg *msg = seaf_ext_msg_new(msg_in, type, user_data);
|
||||
|
||||
if(!msg)
|
||||
return;
|
||||
|
||||
/* If this function is called from main thread, then just show the dialog.
|
||||
* If it's called from another thread, wee need to use g_idle_add_full()
|
||||
* to add this show-dialog action to the main thread
|
||||
*/
|
||||
if (is_main_thread())
|
||||
msgbox_wrapper(msg);
|
||||
else {
|
||||
g_idle_add_full
|
||||
(SEAF_PRIORITY_IDLE, (GSourceFunc)msgbox_wrapper,
|
||||
msg, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool msgbox_yes_or_no_wrapper(SeafExtQuestion *q)
|
||||
{
|
||||
|
||||
GtkWidget *dialog = gtk_message_dialog_new
|
||||
((GtkWindow *)q->user_data,
|
||||
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
GTK_MESSAGE_QUESTION,
|
||||
GTK_BUTTONS_YES_NO,
|
||||
"%s",
|
||||
q->question);
|
||||
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
|
||||
int result = 0;
|
||||
|
||||
if (dialog) {
|
||||
gtk_window_set_title(GTK_WINDOW(dialog), "Seafile");
|
||||
result = gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
|
||||
q->yes_no = (result == GTK_RESPONSE_YES);
|
||||
|
||||
pthread_cond_signal(&q->cond);
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bool msgbox_yes_or_no(char *question_in, void *user_data)
|
||||
{
|
||||
SeafExtQuestion *q = seaf_ext_question_new(question_in, user_data);
|
||||
|
||||
if (!q)
|
||||
return FALSE;
|
||||
|
||||
if (is_main_thread()) {
|
||||
/* in main thread */
|
||||
GtkWidget *dialog = gtk_message_dialog_new
|
||||
((GtkWindow *)q->user_data,
|
||||
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
GTK_MESSAGE_QUESTION,
|
||||
GTK_BUTTONS_YES_NO,
|
||||
"%s",
|
||||
q->question);
|
||||
|
||||
g_free(q->question);
|
||||
g_free(q);
|
||||
|
||||
int result = 0;
|
||||
|
||||
if (dialog) {
|
||||
gtk_window_set_title(GTK_WINDOW(dialog), "Seafile");
|
||||
result = gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
|
||||
return (result == GTK_RESPONSE_YES);
|
||||
|
||||
} else {
|
||||
/* Not in main thread. */
|
||||
bool yes_no = FALSE;
|
||||
if (pthread_mutex_init(&q->mutex, NULL) != 0) {
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
if (pthread_cond_init(&q->cond, NULL) != 0) {
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&q->mutex);
|
||||
|
||||
g_idle_add_full (SEAF_PRIORITY_IDLE,
|
||||
(GSourceFunc)msgbox_yes_or_no_wrapper,
|
||||
q,
|
||||
NULL);
|
||||
|
||||
pthread_cond_wait(&q->cond, &q->mutex);
|
||||
|
||||
yes_no = q->yes_no;
|
||||
|
||||
/* clean up */
|
||||
|
||||
pthread_mutex_unlock(&q->mutex);
|
||||
pthread_mutex_destroy(&q->mutex);
|
||||
pthread_cond_destroy(&q->cond);
|
||||
|
||||
on_error:
|
||||
g_free(q->question);
|
||||
g_free(q);
|
||||
|
||||
return yes_no;
|
||||
}
|
||||
}
|
||||
|
95
desktop/nautilus/seaf-ext.c
Normal file
@ -0,0 +1,95 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
Seafile Nautilus extension type implentation file.
|
||||
*/
|
||||
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <libnautilus-extension/nautilus-menu-provider.h>
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#include "seaf-ext.h"
|
||||
#include "seaf-menu.h"
|
||||
#include "seaf-ext-log.h"
|
||||
#include "seaf-utils.h"
|
||||
|
||||
|
||||
static void
|
||||
seaf_ext_init (SeafExt *self)
|
||||
{
|
||||
seaf_ext_log ("seafile extension instance init");
|
||||
g_object_ref(self);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
seaf_ext_class_init (SeafExtClass *klass)
|
||||
{
|
||||
seaf_ext_log ("seafile extension class init");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
seaf_ext_class_finalize(SeafExtClass *klass)
|
||||
{
|
||||
seaf_ext_log ("seafile ext class finalize");
|
||||
seaf_ext_log_stop();
|
||||
}
|
||||
|
||||
static void
|
||||
menu_iface_init (gpointer g_iface, gpointer iface_data)
|
||||
{
|
||||
seaf_ext_log ("menu_iface_init");
|
||||
NautilusMenuProviderIface *menu_iface = g_iface;
|
||||
|
||||
menu_iface->get_file_items = seaf_get_file_items;
|
||||
menu_iface->get_background_items = seaf_get_background_items;
|
||||
#if NAUTILUS_VERSION <= 2
|
||||
menu_iface->get_toolbar_items = seaf_get_toolbar_items;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/* Currently only the menu-provider interface is implentated */
|
||||
|
||||
G_DEFINE_DYNAMIC_TYPE_EXTENDED
|
||||
(SeafExt, seaf_ext, G_TYPE_OBJECT, 0,
|
||||
G_IMPLEMENT_INTERFACE_DYNAMIC (NAUTILUS_TYPE_MENU_PROVIDER,
|
||||
(GInterfaceInitFunc)menu_iface_init))
|
||||
|
||||
|
||||
static GType seaf_ext_types[1];
|
||||
|
||||
void
|
||||
nautilus_module_initialize (GTypeModule *module)
|
||||
{
|
||||
seaf_ext_log("nautilus_module_initialize");
|
||||
|
||||
if (!seaf_ext_mutex_init()) {
|
||||
seaf_ext_log ("failed to init cache mutex");
|
||||
}
|
||||
|
||||
seaf_ext_register_type(module);
|
||||
seaf_ext_types[0] = seaf_ext_get_type();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
nautilus_module_shutdown (void)
|
||||
{
|
||||
/* Any module-specific shutdown */
|
||||
seaf_ext_log ("module shut down");
|
||||
seaf_ext_log_stop();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
nautilus_module_list_types (const GType **types,
|
||||
int *num_types)
|
||||
{
|
||||
seaf_ext_log("nautilus_module_list_types");
|
||||
*types = seaf_ext_types;
|
||||
*num_types = G_N_ELEMENTS(seaf_ext_types);
|
||||
}
|
22
desktop/nautilus/seaf-ext.h
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef SEAF_EXT_H
|
||||
#define SEAF_EXT_H
|
||||
|
||||
|
||||
#define SEAF_TYPE_EXT (seaf_ext_get_type ())
|
||||
#define SEAF_EXT(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj), SEAF_TYPE_EXT, SeafExt)
|
||||
#define SEAF_IS_EXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAF_TYPE_EXT))
|
||||
|
||||
|
||||
typedef struct _SeafExt SeafExt;
|
||||
typedef struct _SeafExtClass SeafExtClass;
|
||||
|
||||
struct _SeafExt {
|
||||
GObject parent;
|
||||
};
|
||||
|
||||
|
||||
struct _SeafExtClass {
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
#endif /* SEAF_EXT_H */
|
167
desktop/nautilus/seaf-menu.c
Normal file
@ -0,0 +1,167 @@
|
||||
#include "platform.h"
|
||||
#include "seaf-menu.h"
|
||||
#include "seaf-utils.h"
|
||||
#include "seaf-ext-log.h"
|
||||
#include "menu-engine.h"
|
||||
|
||||
|
||||
static NautilusMenuProvider *seaf_menu_provider;
|
||||
|
||||
/* This function is called when a user right-clicks on a file or several
|
||||
* selected files.
|
||||
*/
|
||||
GList *
|
||||
seaf_get_file_items (NautilusMenuProvider *provider,
|
||||
GtkWidget *window,
|
||||
GList *files)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* This function is called to add menu items when a user right-clicks on the
|
||||
* background of a folder; However, it is called when the user entered the
|
||||
* folder, not at the point when the right-click takes place. And It would not
|
||||
* be refreshed unless the user clicks the "refresh" command. We can refresh
|
||||
* it by emitting `items-updated' signal on the `provider'. */
|
||||
GList *
|
||||
seaf_get_background_items (NautilusMenuProvider *provider,
|
||||
GtkWidget *window,
|
||||
NautilusFileInfo *current_folder)
|
||||
{
|
||||
seaf_menu_provider = provider;
|
||||
|
||||
GError *error = NULL;
|
||||
char *uri = nautilus_file_info_get_uri (current_folder);
|
||||
if (!uri)
|
||||
return NULL;
|
||||
|
||||
char *name = g_filename_from_uri(uri, NULL, &error);
|
||||
g_free (uri);
|
||||
if (error || !name)
|
||||
return NULL;
|
||||
|
||||
/* TODO: when to free this SeafMenu ? */
|
||||
SeafMenu *seaf_menu = g_new0(SeafMenu, 1);
|
||||
memcpy(seaf_menu->name, name, strlen(name) + 1);
|
||||
seaf_menu->menu_provider = provider;
|
||||
seaf_menu->window = window;
|
||||
seaf_menu->submenus = NULL;
|
||||
|
||||
g_free (name);
|
||||
|
||||
build_seafile_menu(seaf_menu);
|
||||
if (!seaf_menu->submenus) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NautilusMenuItem *item = nautilus_menu_item_new
|
||||
("Seafile", /* Name */
|
||||
"Seafile", /* label */
|
||||
"Seafile", /* tooltips */
|
||||
SEAF_EXT_UI_DIR "seaf_ext" ICON_EXT); /* icon */
|
||||
|
||||
NautilusMenu *naut_menu = nautilus_menu_new();
|
||||
nautilus_menu_item_set_submenu(item, naut_menu);
|
||||
|
||||
GList *ptr = seaf_menu->submenus;
|
||||
for (; ptr; ptr = ptr->next) {
|
||||
NautilusMenuItem *item = ptr->data;
|
||||
nautilus_menu_append_item(naut_menu, item);
|
||||
}
|
||||
g_list_free(seaf_menu->submenus);
|
||||
|
||||
GList *l = g_list_append(NULL, item);
|
||||
return l;
|
||||
}
|
||||
|
||||
|
||||
#if NAUTILUS_VERSION <= 2
|
||||
GList *
|
||||
seaf_get_toolbar_items (NautilusMenuProvider *provider,
|
||||
GtkWidget *window,
|
||||
NautilusFileInfo *current_folder)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* When a function is added to the main loop by g_idle_add_full(), the main
|
||||
* loop will execute this function on and on if it does not return `FALSE'.
|
||||
* Since nautilus_menu_provider_emit_items_updated_signal() itself doesn't
|
||||
* return FALSE, we need to wrap it.
|
||||
*/
|
||||
static bool item_updated_signal_wrapper(NautilusMenuProvider *provider)
|
||||
{
|
||||
nautilus_menu_provider_emit_items_updated_signal(provider);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void send_refresh_menu_signal(NautilusMenuProvider *provider_in)
|
||||
{
|
||||
NautilusMenuProvider *provider = NULL;
|
||||
|
||||
if (provider_in)
|
||||
/* for update menu after handling a command */
|
||||
provider = provider_in;
|
||||
else
|
||||
/* for update menu periodically */
|
||||
provider = seaf_menu_provider;
|
||||
|
||||
if (provider && NAUTILUS_IS_MENU_PROVIDER(provider)) {
|
||||
|
||||
if (is_main_thread()) {
|
||||
nautilus_menu_provider_emit_items_updated_signal(provider);
|
||||
} else {
|
||||
/* not called from the main thread */
|
||||
g_idle_add_full
|
||||
(G_PRIORITY_DEFAULT_IDLE,
|
||||
(GSourceFunc)item_updated_signal_wrapper,
|
||||
provider, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reset_active_menu(SeafMenu *seaf_menu)
|
||||
{
|
||||
seaf_menu->selection = SEAF_MI_ALWAYS;
|
||||
}
|
||||
|
||||
bool build_menu_item(SeafMenu *seaf_menu, const struct menu_item *mi)
|
||||
{
|
||||
if (!seaf_menu || !mi)
|
||||
return FALSE;
|
||||
|
||||
char *name = mi->string;
|
||||
char *label = name;
|
||||
char *helptext = mi->helptext;
|
||||
char *icon = mi->icon;
|
||||
|
||||
if (!name || !helptext)
|
||||
return TRUE;
|
||||
|
||||
NautilusMenuItem *item = nautilus_menu_item_new
|
||||
(name, /* Name */
|
||||
label, /* label */
|
||||
helptext, /* tooltips */
|
||||
icon); /* icon */
|
||||
|
||||
if (!item) {
|
||||
seaf_ext_log ("Failed to create menu item");
|
||||
return FALSE;
|
||||
}
|
||||
if (mi->op != SEAF_NONE) {
|
||||
g_object_set_data_full((GObject *)item,
|
||||
"seaf_menu",
|
||||
seaf_menu,
|
||||
NULL);
|
||||
|
||||
g_signal_connect((GObject *)item,
|
||||
"activate",
|
||||
(GCallback)dispatch_menu_command,
|
||||
(gpointer)&mi->op);
|
||||
|
||||
}
|
||||
seaf_menu->submenus = g_list_append(seaf_menu->submenus, item);
|
||||
return TRUE;
|
||||
}
|
41
desktop/nautilus/seaf-menu.h
Normal file
@ -0,0 +1,41 @@
|
||||
#ifndef SEAF_MENU_H
|
||||
#define SEAF_MENU_H
|
||||
|
||||
#include <glib.h>
|
||||
#include <libnautilus-extension/nautilus-menu-provider.h>
|
||||
|
||||
typedef struct SeafMenu SeafMenu;
|
||||
|
||||
struct SeafMenu {
|
||||
NautilusMenuProvider *menu_provider;
|
||||
GList *submenus;
|
||||
GtkWidget *window;
|
||||
|
||||
unsigned int count;
|
||||
unsigned int selection;
|
||||
char name[MAX_PATH]; /* the file/dir current clicked on */
|
||||
char repo_id[37]; /* set if in a repo dir */
|
||||
char repo_wt[MAX_PATH]; /* repo top wt, set if in a repo dir */
|
||||
};
|
||||
|
||||
|
||||
GList *
|
||||
seaf_get_file_items (NautilusMenuProvider *provider,
|
||||
GtkWidget *window,
|
||||
GList *files);
|
||||
|
||||
|
||||
GList *
|
||||
seaf_get_background_items (NautilusMenuProvider *provider,
|
||||
GtkWidget *window,
|
||||
NautilusFileInfo *current_folder);
|
||||
|
||||
|
||||
GList *
|
||||
seaf_get_toolbar_items (NautilusMenuProvider *provider,
|
||||
GtkWidget *window,
|
||||
NautilusFileInfo *current_folder);
|
||||
|
||||
void send_refresh_menu_signal(NautilusMenuProvider *menu_provider);
|
||||
|
||||
#endif
|