Add deiban, desktop dirs in order to build deb package.

This commit is contained in:
gnehzuil 2012-08-09 21:47:06 +08:00
parent 079716eb75
commit 64067dd62a
72 changed files with 5519 additions and 4 deletions

View File

@ -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}

View File

@ -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
View File

@ -0,0 +1 @@
SUBDIRS = icons

View File

@ -0,0 +1,4 @@
icondir = $(datadir)/icons/hicolor/16x16/apps
icon_DATA = seafile.png
EXTRA_DIST = $(icon_DATA)

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 B

View File

@ -0,0 +1,4 @@
icondir = $(datadir)/icons/hicolor/22x22/apps
icon_DATA = seafile.png
EXTRA_DIST = $(icon_DATA)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,4 @@
icondir = $(datadir)/icons/hicolor/24x24/apps
icon_DATA = seafile.png
EXTRA_DIST = $(icon_DATA)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,4 @@
icondir = $(datadir)/icons/hicolor/32x32/apps
icon_DATA = seafile.png
EXTRA_DIST = $(icon_DATA)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,4 @@
icondir = $(datadir)/icons/hicolor/48x48/apps
icon_DATA = seafile.png
EXTRA_DIST = $(icon_DATA)

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

20
data/icons/Makefile.am Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

6
debian/README.Debian vendored Normal file
View 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
View 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
View File

@ -0,0 +1 @@
7

13
debian/control vendored Normal file
View 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
View 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
View File

@ -0,0 +1,2 @@
usr/bin
usr/sbin

0
debian/docs vendored Normal file
View File

115
debian/rules vendored Executable file
View 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
View 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
View File

@ -0,0 +1,5 @@
if WIN32
SUBDIRS = explorer
else
SUBDIRS = nautilus
endif

132
desktop/README Normal file
View 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

View 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}"

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View 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;
}

View 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
View 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 */

View 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 */

View 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);
}
}

View 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__) \

View 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
View 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);
}

View 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
View 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
View 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
View 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
View 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
View 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;
}

View 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[]);

View 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
View 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;
}

View 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 */

View 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
};

View 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 */

View 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;
}

View 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 */

View 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);
}

View 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 */

View File

@ -0,0 +1,9 @@
LIBRARY seafile_shell_ext
EXPORTS
DllMain
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
DllCanUnloadNow PRIVATE
DllGetClassObject PRIVATE
DllInstall PRIVATE
TerminateSeafile

View 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
View 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;
}

View 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;
}
}

View 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);
}

View 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 */

View 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;
}

View 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