Merge tag 'upstream/0.5.11'

Upstream version 0.5.11
This commit is contained in:
Sergio Durigan Junior 2015-09-11 20:47:13 -04:00
commit bb954d02ba
384 changed files with 235692 additions and 107186 deletions

16
.gitignore vendored
View file

@ -1,16 +0,0 @@
Makefile
.waf-*
.lock-wscript
_build
po/.intltool-merge-cache
po/LINGUAS
po/POTFILES
po/midori.pot
po/stamp-it
po/*.gmo
midori.desktop
packages.version

294
CMakeLists.txt Normal file
View file

@ -0,0 +1,294 @@
# Copyright (C) 2013 Christian Dywan <christian@twotoasts.de>
cmake_minimum_required(VERSION 2.6)
cmake_policy(VERSION 2.6)
# Work-around a bug in the included FindGettext fixed with 2.8.8
# See http://www.cmake.org/pipermail/cmake-commits/2012-February/012117.html
if ("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}" VERSION_LESS "2.8.8")
cmake_policy(SET CMP0002 OLD)
endif ()
project(midori C)
add_definitions("-DPACKAGE_NAME=\"${CMAKE_PROJECT_NAME}\"")
add_definitions("-DPACKAGE_BUGREPORT=\"https://bugs.launchpad.net/midori\"")
set(VERSION 0.5.11)
add_definitions("-DMIDORI_VERSION_SUFFIX=\"${VERSION}\"")
string(REPLACE "." ";" VERSION_LIST ${VERSION})
LIST(GET VERSION_LIST 0 MIDORI_MAJOR_VERSION)
add_definitions("-DMIDORI_MAJOR_VERSION=${MIDORI_MAJOR_VERSION}")
LIST(GET VERSION_LIST 1 MIDORI_MINOR_VERSION)
add_definitions("-DMIDORI_MINOR_VERSION=${MIDORI_MINOR_VERSION}")
LIST(GET VERSION_LIST 2 MIDORI_MICRO_VERSION)
add_definitions("-DMIDORI_MICRO_VERSION=${MIDORI_MICRO_VERSION}")
execute_process(COMMAND "bzr" "revno"
OUTPUT_VARIABLE REVISION
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE)
if (REVISION)
set(VERSION "${VERSION}~r${REVISION}")
# All warnings are errors in development builds
set(VALAFLAGS ${VALAFLAGS} --fatal-warnings)
set(CFLAGS "${CFLAGS}")
endif ()
add_definitions("-DPACKAGE_VERSION=\"${VERSION}\"")
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
# Disallow building during install to avoid permission problems
set(CMAKE_SKIP_INSTALL_ALL_DEPENDENCY 1)
find_package(Vala REQUIRED)
vala_require("0.16.0")
set(VALAFLAGS ${VALAFLAGS}
--enable-deprecated
--debug
)
include(GNUInstallDirs)
set(DATADIR ${CMAKE_INSTALL_FULL_DATADIR})
add_definitions("-DMDATADIR=\"${DATADIR}\"")
add_definitions("-DSYSCONFDIR=\"${CMAKE_INSTALL_FULL_SYSCONFDIR}\"")
add_definitions("-DLIBDIR=\"${CMAKE_INSTALL_FULL_LIBDIR}\"")
add_definitions("-DDOCDIR=\"${CMAKE_INSTALL_FULL_DOCDIR}\"")
add_definitions("-DENABLE_NLS=1")
add_definitions("-DLOCALEDIR=\"${CMAKE_INSTALL_FULL_LOCALEDIR}\"")
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/config.h" "/* # generated file (stub) */")
add_definitions("-DHAVE_CONFIG_H=1")
find_file (UNISTD unistd.h)
if (UNISTD)
add_definitions("-DHAVE_UNISTD_H")
endif ()
if (UNIX)
find_file (SIGNAL signal.h)
if (SIGNAL)
add_definitions("-DHAVE_SIGNAL_H")
endif ()
find_file (EXEC_INFO execinfo.h)
if (EXEC_INFO)
set(VALAFLAGS ${VALAFLAGS} -D HAVE_EXECINFO_H)
endif ()
endif ()
find_library (X11 X11)
if (X11)
# Pass /usr/X11R6/include for OpenBSD
find_file (SCRNSAVER X11/extensions/scrnsaver.h /usr/X11R6/include)
find_library (XSS Xss /usr/lib/X11R6/lib)
if (SCRNSAVER AND XSS)
add_definitions("-DHAVE_X11_EXTENSIONS_SCRNSAVER_H")
set(OPTS_LIBRARIES "${OPTS_LIBRARIES};${XSS};${X11}")
endif ()
endif ()
if (WIN32)
set(VALAFLAGS ${VALAFLAGS} -D HAVE_WIN32)
endif ()
if (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
set(VALAFLAGS ${VALAFLAGS} -D HAVE_FREEBSD)
endif ()
if (${CMAKE_SYSTEM_NAME} MATCHES "DragonFly")
set(VALAFLAGS ${VALAFLAGS} -D HAVE_DRAGONFLY)
set(DFLY 1)
endif ()
if (APPLE)
add_definitions("-DHAVE_OSX=1")
set(VALAFLAGS ${VALAFLAGS} -D HAVE_OSX)
else ()
add_definitions("-DHAVE_OSX=0")
endif ()
find_package(PkgConfig)
pkg_check_modules(DEPS REQUIRED
libxml-2.0>=2.6
sqlite3>=3.6.19
gmodule-2.0
gio-2.0>=2.32.3
libsoup-gnome-2.4>=2.37.1
)
add_definitions("-DHAVE_LIBXML")
add_definitions("-DGIO_VERSION=\"${DEPS_gio-2.0_VERSION}\"")
add_definitions("-DLIBSOUP_VERSION=\"${DEPS_libsoup-gnome-2.4_VERSION}\"")
set(PKGS posix linux libxml-2.0 sqlite3 gmodule-2.0 gio-2.0 libsoup-2.4)
if (${DEPS_libsoup-gnome-2.4_VERSION} VERSION_GREATER "2.40.0")
# valac 0.16 didn't have the bindings yet
# For consistency we need to ensure C code makes the same assumptions
if (${VALA_VERSION} VERSION_GREATER "0.17.0")
add_definitions("-DHAVE_LIBSOUP_2_40_0")
set(VALAFLAGS ${VALAFLAGS} -D HAVE_LIBSOUP_2_40_0)
endif ()
endif ()
if (${DEPS_libsoup-gnome-2.4_VERSION} VERSION_GREATER "2.48.0")
add_definitions("-DHAVE_LIBSOUP_2_48_0")
set(VALAFLAGS ${VALAFLAGS} -D HAVE_LIBSOUP_2_48_0)
endif ()
if (${DEPS_gio-2.0_VERSION} VERSION_GREATER "2.40.0" OR WIN32)
add_definitions("-DLIBNOTIFY_VERSION=\"No\"")
else ()
pkg_check_modules(NOTIFY REQUIRED libnotify)
add_definitions("-DLIBNOTIFY_VERSION=\"${NOTIFY_VERSION}\"")
add_definitions("-DHAVE_LIBNOTIFY")
set(OPTS_INCLUDE_DIRS "${OPTS_INCLUDE_DIRS};${NOTIFY_INCLUDE_DIRS}")
set(OPTS_LIBRARIES "${OPTS_LIBRARIES};${NOTIFY_LIBRARIES}")
set(PKGS ${PKGS} libnotify)
endif ()
option(USE_GTK3 "Use GTK+3" OFF)
option(HALF_BRO_INCOM_WEBKIT2 "Serve as a guniea pig" OFF)
option(USE_ZEITGEIST "Zeitgeist history integration" ON)
option(USE_GRANITE "Fancy notebook and pop-overs" OFF)
option(USE_APIDOCS "API documentation" OFF)
option(USE_GIR "Generate GObject Introspection bindings" OFF)
option(EXTRA_WARNINGS "Additional compiler warnings" OFF)
# GTK+3 is implied here, whether set or not
if (USE_GRANITE OR HALF_BRO_INCOM_WEBKIT2)
set(USE_GTK3 ON)
endif ()
if (USE_GRANITE)
pkg_check_modules(GRANITE granite>=0.2)
set(OPTS_INCLUDE_DIRS "${OPTS_INCLUDE_DIRS};${GRANITE_INCLUDE_DIRS}")
set(OPTS_LIBRARIES "${OPTS_LIBRARIES};${GRANITE_LIBRARIES}")
add_definitions("-DHAVE_GRANITE")
add_definitions("-DGRANITE_VERSION=\"${GRANITE_VERSION}\"")
set(VALAFLAGS ${VALAFLAGS} -D HAVE_GRANITE)
set(PKGS ${PKGS} granite)
else ()
add_definitions("-DGRANITE_VERSION=\"No\"")
endif()
if (USE_ZEITGEIST)
pkg_check_modules(ZEITGEIST zeitgeist-2.0>=0.3.14)
set(OPTS_INCLUDE_DIRS "${OPTS_INCLUDE_DIRS};${ZEITGEIST_INCLUDE_DIRS}")
set(OPTS_LIBRARIES "${OPTS_LIBRARIES};${ZEITGEIST_LIBRARIES}")
add_definitions("-DHAVE_ZEITGEIST")
set(PKGS ${PKGS} zeitgeist-2.0)
endif()
if (WIN32)
add_definitions("-DGCR_VERSION=\"No\"")
else ()
if (USE_GTK3)
pkg_check_modules(GCR REQUIRED gcr-3>=2.32)
else ()
pkg_check_modules(GCR REQUIRED gcr-base-3>=2.32)
endif ()
add_definitions("-DGCR_VERSION=\"${GCR_VERSION}\"")
add_definitions("-DHAVE_GCR")
set(OPTS_INCLUDE_DIRS ${OPTS_INCLUDE_DIRS} ${GCR_INCLUDE_DIRS})
set(OPTS_LIBRARIES ${OPTS_LIBRARIES} ${GCR_LIBRARIES})
endif ()
if (HALF_BRO_INCOM_WEBKIT2)
pkg_check_modules(DEPS_GTK REQUIRED
gtk+-3.0>=3.10.0
webkit2gtk-4.0>=2.3.91
)
add_definitions("-DHAVE_WEBKIT2")
add_definitions("-DGTK_VERSION=\"${DEPS_GTK_gtk+-3.0_VERSION}\"")
add_definitions("-DWEBKIT_VERSION=\"${DEPS_GTK_webkit2gtk-4.0_VERSION}\"")
set(PKGS ${PKGS} gtk+-3.0)
# set(EXTRA_VAPIS ${EXTRA_VAPIS} "${CMAKE_SOURCE_DIR}/midori/webkit2gtk-web-extension-4.0.vapi")
set(EXTRA_VAPIS ${EXTRA_VAPIS} "${CMAKE_SOURCE_DIR}/midori/webkit2gtk-4.0.vapi")
set(VALAFLAGS ${VALAFLAGS} -D HAVE_GTK3)
set(VALAFLAGS ${VALAFLAGS} -D HAVE_WEBKIT2)
set(VALAFLAGS ${VALAFLAGS} -D HAVE_WEBKIT2_3_91)
elseif (USE_GTK3)
pkg_check_modules(DEPS_GTK REQUIRED
gtk+-3.0>=3.10.0
webkitgtk-3.0>=1.8.1
javascriptcoregtk-3.0
)
add_definitions("-DGTK_VERSION=\"${DEPS_GTK_gtk+-3.0_VERSION}\"")
add_definitions("-DWEBKIT_VERSION=\"${DEPS_GTK_webkitgtk-3.0_VERSION}\"")
set(PKGS ${PKGS} gtk+-3.0)
set(EXTRA_VAPIS ${EXTRA_VAPIS} "${CMAKE_SOURCE_DIR}/midori/webkitgtk-3.0.vapi")
set(VALAFLAGS ${VALAFLAGS} -D HAVE_GTK3)
else ()
pkg_check_modules(DEPS_GTK REQUIRED
gtk+-2.0>=2.24.0
webkit-1.0>=1.8.1
javascriptcoregtk-1.0
)
add_definitions("-DGTK_VERSION=\"${DEPS_GTK_gtk+-2.0_VERSION}\"")
add_definitions("-DWEBKIT_VERSION=\"${DEPS_GTK_webkit-1.0_VERSION}\"")
set(PKGS ${PKGS} gtk+-2.0)
set(EXTRA_VAPIS ${EXTRA_VAPIS} "${CMAKE_SOURCE_DIR}/midori/webkitgtk-3.0.vapi")
endif ()
set(EXTRA_VAPIS ${EXTRA_VAPIS} "${CMAKE_SOURCE_DIR}/katze/katze.vapi")
# dh_translations detects this if there's no variable used
set (GETTEXT_PACKAGE "midori")
add_definitions("-DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\"")
set(CFLAGS "${CFLAGS} -Wall -Wundef -Wno-deprecated-declarations -g")
if (EXTRA_WARNINGS)
LIST(APPEND EXTRA_CFLAGS_LIST
-Wextra
-Wno-unused-parameter
-Wno-missing-field-initializers
-Wno-comment
-Waggregate-return
-Wredundant-decls
-Wshadow -Wpointer-arith -Wcast-align
-Winline -Wformat-security -fno-common
-Winit-self -Wundef
-Wnested-externs
)
string(REPLACE ";" " " EXTRA_CFLAGS "${EXTRA_CFLAGS_LIST}")
set(CFLAGS "${CFLAGS} ${EXTRA_CFLAGS}")
else ()
if (REVISION)
set(CFLAGS "${CFLAGS} -Werror")
endif()
endif ()
# Explicitly add -fPIC for older toolchains
set(VALA_CFLAGS "-g -fPIC")
# With compiler versions that can, enable exactly the non-spurious warnings
# in Vala-generated C, otherwise disable warnings
if ((CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER "5.0.0")
OR (CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER "3.0.0"))
set(VALA_CFLAGS "${VALA_CFLAGS} -Werror=implicit-function-declaration")
set(VALA_CFLAGS "${VALA_CFLAGS} -Wno-incompatible-pointer-types")
set(VALA_CFLAGS "${VALA_CFLAGS} -Wno-discarded-qualifiers")
set(VALA_CFLAGS "${VALA_CFLAGS} -Wno-deprecated-declarations")
else ()
set(VALA_CFLAGS "${VALA_CFLAGS} -w")
endif ()
set(LIBMIDORI "${CMAKE_PROJECT_NAME}-core")
# CMake provides no uninstall target by design
add_custom_target (uninstall
COMMAND "xargs" "rm" "-v" "<" "install_manifest.txt")
install(FILES AUTHORS COPYING ChangeLog EXPAT README DESTINATION ${CMAKE_INSTALL_DOCDIR})
add_subdirectory (midori)
add_subdirectory (extensions)
enable_testing()
add_subdirectory (tests)
add_subdirectory (po)
add_subdirectory (icons)
add_subdirectory (data)
add_subdirectory (config)
if (USE_APIDOCS)
add_subdirectory (docs/api)
endif ()
if (USE_GIR)
add_subdirectory (gir)
endif ()

403
ChangeLog
View file

@ -1,5 +1,408 @@
This file is licensed under the terms of the expat license, see the file EXPAT.
v0.5.11
Add fake theme for built-in icons
* Don't truncate long speed dial titles if there's room to display them
Fix warnings for -Wformat-security
Ensure vala knows the prototypes of functions it calls, fixing pointer truncation in tests
Add unit test to check appmenu/menubar visibility
Fix last known GTK2 entry placeholder text bugs
Make sure that only one of appmenu and menubar are visible *initially* as well as when changed
Move adblock icons to hicolor
Limit bookmarks panel callbacks to the lifetime of the panel to fix a crash
Fix fallout (broken bookmarks and history panel search) from tweaks to GTK2 entry placeholder
fix property binding to ensure that exactly one of appmenu button and menubar is always visible
Skip open-with codepath with abp links, they are internal
Use find_file to locate execinfo.h
Fix middle/ctrl/normal clicking bookmarks (not folders) in the bookmarkbar.
Add copright header to sanitize_bar.sh
Adblock fixup: Escape . in filter with \
Don't shadow variable uri in midori_browser_save_uri
Switch Adblock icons to 24px color
Always include app menu in toolbar
Fix various mis[sing ]annotations and style issues in GIR
Compile typelib from gir
Fix assert when resetting webapp state after inactivity reset
clean up handling of double-valued db column in Tabby
Add a comment to explain MidoriBrowser popup callback
fix warnings printed when right-clicking resize grip between location and search entries
Win32: Use Dr. MinGW if present to preserve crash info
Fix menubar warning caused by direct cast instead of `as`
Helper script for setting up bzr with some usefull plugins and settings
Stop using Gtk.Entry.max_width_chars
avoid deprecated SoupServer API with libsoup 2.48
Use unowned in foreach loops in Midori.Window
Use unowned in foreach loops in Midori.Completion
Use unowned with Adblock.Subscription and Element in foreach loops
Use unowned strings in foreach loops
Enable openWith in app mode and make it work with view-new
Implement Midori.Window class with toolbar/ headerbar
Drop support for libsoup-gnome-2.4 < 2.37.1
Make search icons for engines work correctly
Move to WebKit2 4.0 which broke ABI
Port to zeitgeist-2.0
win32: Bump shipped GrayBird theme version to fix some rendering issues
avoid deprecated GtkDialog API with GTK+2 >= 2.22
Title case for "Export Certificate" button
fix incorrect type of MAX(sorting) in Tabby
v0.5.10
use exit instead of return in license script
Fix HAVE_GCR guards after GtkPopover port
Remove example app and .desktop before creating it in the unit test
Fix cache dir path in Adblock and always mkdir tmp
Port location action from Granite.PopOver to Gtk.Popover
Match https site when user-style is using domain syntax
Always disable developer tools on Win32
Reimplement Midori.URI.unescape and add various tests
Make the inspector resizable with GTK3 by packing into a GtkScrolledWindow
Don't build tabs2one in release builds
Don't assume GNotification works on Win32
update copyright date in About dialog
Don't entity-escape history and bookmark results in location completion
Only set tabs' error state if errors come from the main frame
Implement Paste and Proceed as an action
No Gcr on Win for the moment
Yet another Speed Dial CSS update:
Port bookmark popover from Granite to Gtk.Popover
Make application choosers resizable with a sane default size
Use GNotification >= 2.40 and use Midori.App API in webmedia
Rework mouse button handling in KatzeArrayAction
Don't bind :day in HistoryDatabase.query
Make GCR mandatory for all builds
Update coub support in mediaHerald
history-list: Fix gtk+3 build caused by dropping "using Gtk;"
Drop all remaining usages of "using *;"
Don't open search engines menu when clearing search action
Only remove apps in the sidepanel when left-clicking the delete icon
Improve robustness of GTK3-compatibility placeholder text fallback
Clean up vapi dependency
tls_flags from webkit_web_view_get_tls_info need to be 0
Don't add failed pages to history
Throw error for wrong paramter in Statement.bind
Replace NoJS "allow all pages" setting with "allow local pages"
Avoid bugs due to race condition in addons delete dialog
Calculate transfer progress at regular intervals to fix 0B/s bug and recalcitrant progess bars
Fix warnings occurring with EXTRA_WARNINGS
Escape parentheses in adblock_fixup_regexp()
Use File.query_exist() on win32 when checking for db to attach
Handle _NEW_WINDOW_ACTION explicitly to make _blank targets work
Fix undefined behavior uint in mouse gestures
fix JavaScript keyup event by calling inherited key-release-event handler in MidoriBrowser
Inline renaming of speed dials
Handle current_size and last_size of Download being equal
Add proper copyright headers to element_hider and autosuggestcontrol
Add X-GNOME-UsesNotifications to indicate the use of notifications
Fix typo in Bookmarks menu UI definition
v0.5.9
Remove dead code from browser and preferences
Build-fix: Make PanedAction's Child.widget public
fixes tab history undo
Set a placeholder text on the URL entry
Add "Add Bookmark" to menu
Show search menu upon left icon click in location bar
Fix crash when saving with associated resources
Fix webkit2 downloads based on older branch
don't hide window decorations for Midori-Granite
Connect bookmarks-db singleton correctly to fix menus
Fix some symbol names and transfer annotations in doc comments
Use correct signature for window-state-event handler
Do not overescape page titles in view completion
Make adblock skip non-standard last update metadata strings
Drop deprecated Granite LightWindow used for the Clear Private Data dialog
Keep storing the last web media tab played.
Allocate CookiePermissionManagerModalInfobar correctly
Make middle clicking reload button duplicate the current tab, similar to other browsers
Use network-changed of GNetworkMonitor to reload all tabs if network becomes available
Show different messages based on network connectivity.
Fix crash when activating the edit menu
Fix "open all in tabs" for bookmarks
Fix a few simple leaks
Don't focus the locationaction when leaving blank pages
Fix leaks of two references to the MidoriApp in Tabby
Compile with valac 0.16 again
Never display about:new in the urlbar
fix crash right-clicking forms on local pages
Share 'youtube, vimeo, dailymotion' that you are playing in Midori using org.midori.mediaHerald
Give the SoupURI a path when checking cookie relevance
Resolve ellipsis and title stripping in completion
Add www. and .com/.country_domain and proceed with Ctrl+Enter/Shift+Enter with (readable code)
Clean up browser tab/ uri/ title notify
Drop pseudo Granite distinction in completion layout
Fix visibility of SpeedDial, Toolbar, Bookmarkbar context menu items
Distinguish between desc file missing and other parsing issues
Use dependencies to clear test folders before execution
win32: Drop dropbox usage from win release script, rename resulting output files
v0.5.8
Use png icon instead of svg in set_status
We must not pass a Cancellable to FaviconDatabase.get_favicon_pixbuf
Retain spelling suggestion menu items from WebKit
Properly guard usage of gtk3 get_style_context
Mimic the look of Granite.DynamicNotebook when compiled with --enable-granite.
Fix X11 lib underlink in midori-core
Fix bookmarkbar bookmark click not opening links
Use sanitized app URI as wm_classname/ StartupWMClass
Make trunk build with WebKit2 again
Fix for incorrect tstamp for background tabs
Don't declare sorting doubles are nullable and print values when database tracing is enabled
Correctly apply saved entry state and treat urlbar as a regular editable item
Add missing conditional includes for granite flavoured build
Open URIs dragged on tab label or new tab button
Small adblock bugfixes
Work around GTK3's hard-coded minimum stackswitcher button width
Fix building with mingw packages from fedora 18
Set page title as basis for print filename
Rename notes inline
Use EXTRA_WARNINGS option when building for windows
Drop forgotten clutter init and obsolete header declarations
Rework history-step handling and make it work again
Port Tabby to DatabaseStatement API
Replace bookmark stracing with generic profiling in Midori.Database
Port autocompleter test to async job
Finishing touches for Adblock
Add filters and defaults
Implement and use ContextAction.escaped
printf URI in show_message_dialog for download error
Improve docs and GIR annotations for KatzeItem, KatzeArray, and MidoriWebSettings
Drop redundant TabNew from compact menu and put button in Tab Panel
Fix loading file:// pages
Implement Send Page Link by Email
Use GtkStackSwitcher with GTK+ >= 3.10
Implements context popup menu on menu entries of bookmark bar and bookmark menu.
Fix building with newer mingw versions
Display locationbar suggestions in the correct order
Don't bother adblocking internal pages and favicons
Don't use trailing comma on last list element in Adblock tests
Rewrite Adblock more modularly, add Whitelist support
Add support of DragonFlyBSD
Change tooltips of Reload and ReloadStop actions while shift modifier is pressed
Implement Midori.Database.attach method
Allow :memory: as folder to make schema detection work
More robust app/ profile creation
Add helper callbacks to modify bookmark's tree store with unneded access to bookmarks db
Implement more flexible fallback behavior for Cookie Permissions
v0.5.7:
Modify actions and internal items in browser without changing settings
Delay tab loading after Midori crashed
Uncomment failing assertions about view_source in tab test
Fallback to about:home if startup is anything but blank
Don't try to create formhistory database if config_dir is NULL
Handle url arguments for blank sessions
Execute commands given at start time
Introduce high-level prepare/ DatabaseStatement API
Drop unused GraniteClutter-based animation support
Drop uncommented contractor support
Drop deprecated StaticNotebook used in KatzePreferences
Introduce notebook class converging separate implementations
Work around symbol relocation issue old version of gcc present on Ubuntu LTS
NULL-check treeview in midori_search_action_get_editor
Adjust CMakeList .ico check to not skip nojs icons
Enable sidepanel in private mode
Move Preferences menu entry above About
Set minimum value of 0 on spin button for maximum cache size
Give NextForward its own label for toolbar editor
Correctly disable favicon database in app and private mode
Change preferences to refer to proxy address as a "URI" (not "hostname")
Add close tabs to right feature
Allow printing without confirmation dialog on kiosk setups
v0.5.6:
instead of creating devpet status icon on extension load, create it only to show new messages
Open speed dial or homepage according to preference
handle tab duplication
Add copyright note to appdata file
Tweak searching for resources when running from build folder
Swap NULL-check with main frame check
Use correct signal when clearing the trash
Hide WEbGL preference if it is unavailable
Remove stored popup sessions from the database
Check all browsers for opened sessions and whether they're popups
removed unused preference dialog and related code
Fix check for found valac and mention VALAC variable
Fix autoscrolling if page contains a frame with our custom error page
Don't use context-menu signal in WebKitGTK+ < 1.10.0
Fix building on Ubuntu 12.04
Reset item ids when re-importing bookmarks
Check path being NULL in export before trying to inspect it
restore the last closed sessions if no session is opened
Cast WebKitDOMHtmlElement for getting source content
Use font-set signal and font family for GTK+ 3.2 font chooser
add function to view dom source
remove unused variable
Resolve compiler warnings in current trunk
Update win32-release script for cmake, move unused docs/scripts to old folder
Try to handle previous runs of cmake in configure wrapper
Correct view source assertions in tab unit test
Build fix: found undeclared in midori_bookmarks_db_remove_item_recursive
Cache bookmark items to avoid their recreation on database reads
allow "view source" on about pages
Enable old target policy on cmake < 2.8.8
Re-arrange data file installing to be more explicit
option to modify the number of tabs which will be restored in each idle callback
Implement MidoriBookmarksDatabase class by inheritence from MidoriDatabase
Ensure tab spinners update as often as the menubar spinner to avoid desync
Use tabby sorting increment when importing session.xbel tabs
Only install config files to /etc if prefix equals /usr
handle urls as argument when starting midori
Make tabby compile with Webkit2
Drop waf build system and provide cmake-based "configure" script
midori_panel_action_activate_cb forgot to update the action group
Fixes bug where certificate Security overlay failed to close
handle tab movement
add tab sorting
Untangle implicit GTK+3 for Granite and WebKit2
Allow running test under debug tools with cmake
Install config files to /etc when install prefix is /usr
Add missing PO_FILES argument to GETTEXT_PROCESS_PO_FILES
Add USE_APIDOCS to build API docs with CMake
Rasterize SVG to PNG with rsvg-convert
fix bookmarks test regression after fix-1179200-4
Add CMakeLists.txt for config directory
Install mo files in locale dir
don't change uri/title if the tab isn't loaded
use a separate signal to store the tab title
Check if execinfo.h header exists on BSD
fix endless loop in Midori.Database.init
Use destructive-action style class in ClearPrivateData
Initialize priv->element to avoid crash when freeing
Introduces KatzeArray::update-item to handle metadata changes
Refactor excuting schema from file into a function
Use stock as string in liststore
Drop needless (and wrong) HAVE_LIBNOTIFY in preferences
Flip horizontal position of the overlay when hit by the mouse
Add Midori.URI.get_base_domain and use it in NoJS
Introduce Midori.Database and use for history and tabby
ctrl+shift+w should trigger a delete-event
Implement dialog windows opened via javascript
Make get_res_filename work with different hierarchies
fix check for new database
Speed up session import
Import tab title from old sessions
Separate CFLAGS for C and add missing HAVE_
Install top-level text files and FAQ html/ css to doc dir
Provide and install .appdata.xml file for app stores
Move bookmarks db handling to midori-bookmarks-db
Add XSS to OPTS_LIBRARIES
Update condition for UBUNTU_MENUPROXY to work on Saucy
Introduce tabby, the new session manager
Fix typo in katze_item_set_meta_integer call
Allow bookmark bar update on additions resulting from imports
Re-work midori_array_query_recursive to not include folder items twice
Fix syntax of icon sizes passed to foreach
Add bzr revision number to version if available
Unify nojs and cookie policy dialogs, make policy changeable within the list
Drop all G_ENABLE_DEBUG guards
Add -g to CFLAGS to enable debugging symbols
Adjust cmake build for Win32
Implement CMake build setup
Port MidoriApp from Unique/ sockets to GApplication
New signal about-content to provide content for about uris
Check if browser is NULL in midori_view_get_tab_menu to prevent a crash. Fixes bug #1215652.
Ensure proxy setting widgets callbacks don't outlive the widgets themselves
Fix webkit2 build error
Show the bookmarks import location combobox.
Rename internal completion URLs to avoid confusion
v0.5.5:
Fix name and text fields inversion in XBEL folder import
Correct packing of cookie and nojs permission dialog.
Don't set tab title/special when a non-main frame displays an error
Revise "cookies" debug output, merge expiry check and disallow revival of old cookies
Drop now unused cgit module.xml file
Use SoupProxyResolverGnome unconditionally and disable prefetching if proxy is active
win32: Hide gui for profiles in webapp manager, as they are currently broken on Windows
win32: support additional mouse buttons for going back/forward in history
Enrich app error messages with filenames
Fix segfault if url contains " %00"
Replace 'Run as app' in bookmark dialog with 'Create launcher'
Split config files and install from folders recursively
Implement GTK+ theme switching via Preferences (Win32)
Enable set_disk_cache_directory with WebKit2
Introduce Midori.ContextAction and refactor page menu from scratch
Define large dialog icon size relative to dialog icon size
Extension Devpet which shows error messages and backtraces in systray
WebKit2 cookie support
Check the hit test result for editable to see if , should search
Use SoupCookieJarSqlite and drop KatzeHttpCookies(Sqlite)
Show folder tree when editing bookmarks
Handle double value in _midori_browser_activate_action
Add privacy preferences in web app mode
Escape parentheses in adblock_fixup_regexp
Introduce object oriented API for access to History Database
Allow rss feeds with version 0.92
Rename History completion to Bookmarks and History
Don't show rss feed icon on twitter, underlying API was retired
Read apps/ profiles from folder, leave launchers separate
Fill in bookmark folder attributes in bookmarkbar populate
v0.5.4:
Refactor history step and allow multiple title updates
Call midori_browser_connect_tab with correct type
Don't add HistoryCompletion if there's no history
Restore reload button icon in error pages
Don't insert folders into the log
If an url is specified the fallback url should not be loaded
Fixed crashes when closing a loading tab + granite's tab moving
Test if plugins are redundant instead of skipping them all
Avoid selecting bookmark uris that begin by 'javascript:' for completion
Set FOREIGN_KEYS pragma on db initialization
Implement a default zoom level preference
Fix tautological use of G_MAXINT with enum
Take current selection into account for bookmark folders when adding/editing bookmark
Improve error page visuals, show suggestions on network errors
Bump vala to 0.16.0
Downgrade glib requirement to 2.32.3 to re-enable building under Ubuntu 12.04 (LTS)
Bump glib2 version to 2.32.4
Improve and unify thumbnail generation
Omit speed dial and blank pages from view completion
Makes the elements of the speed dial non-selectable
Use NULL-safe comparison in katze_item_icon_loaded_cb
Drop non-DOM style sheet injection code path
Clean small leftovers from GTK and WebKit version bumps
Bump GTK+ requirement to 2.24 and drop support for earlier versions
Check for app mode to set browser icon instead of readonly
Escape square brackets in adblock_fixup_regexp
Fix showing (sub)folders in bookmarkbar
Bump WebKit requirement to 1.8.3 and drop support for earlier versions
Set menu on dynamic notebook tab
Do not run toolbar editor's GtkDialog in its own main loop by prevent calling gtk_dialog_run(). Instead just set the GtkDialog modal and show it.
Remove unnecesary harmful code from tab_switched_cb
Fix segfault when deleteing tabs with history list
Specify int64 id item as a string in bookmark remove/update queries
Distinguish between box and event box in the tab label when colouring tabs
Show visual feedback when hovering over items in bookmark panel
Replace INSTALL/ HACKING with exported Contribute wiki page
Delete tabs from history list with Del
Check brightness of backgroung color when deciding foreground color of given tab
Clean launcher filenames, double-click to open and delete button
Avoid declaring browser twice within the same function
Add ./waf --update-pot
Fix memory leak introduced in r6184
Use old function name g_dbus_generate_guid for old valac
Move Import and Export into menu Bookmarks
Collect multiple download notifications within a minute
Fix segfault when right clicking on a web view.
Make libnotify mandatory except on Windows
Remove the rather unnecessary ./waf --run feature
Send a notification after creating a launcher
Ambiguous 'Open as App' context menu item was removed
Apply label color to label rather than event box
Store data of app mode based on URL in ~/.local/share/midori/apps
Split colorful tabs code into helper functions and add unit tests
Fix History List memory leak when closing Midori window.
Replace .gitignore with a .bzrignore
Always define GCR_VERSION in GTK+2 build
Fix bookmarks dialog rename regression introduced in r6167.
Drop check for gcr-3-gtk2 which isn't being maintained.
Scrap unneeded background variables in location renderer callbacks
Title case and proper packing in bookmark dialog
Delete PO files Launchpad spewed into root directory when it couldn't find po/*.pot file.
Issue a warning when trying to use MIDORI_DEBUG while running
Update dates to 2013 to fix bug #1167075.
v0.5.2:
Re-release with a proper version number and changelog

27
GNUmakefile.in Normal file
View file

@ -0,0 +1,27 @@
# Based on "http://iany.me/wiki/Makefile/" by "Ian Yang" licensed under "CC by 3.0"
BUILD_FOLDER := _build
CUSTOM_TARGETS := cmake
# Do not try to use custom target when invoking external makefile
EXTERNAL_TARGETS := $(filter-out $(CUSTOM_TARGETS), $(MAKECMDGOALS))
# Call all targets using `Makefile` in build directory in one `make` command.
$(or $(lastword $(EXTERNAL_TARGETS)),all):
$(MAKE) -C $(BUILD_FOLDER) $(EXTERNAL_TARGETS)
# If no targets are specified, use the dummy `all` target
.PHONY: $(EXTERNAL_TARGETS) all
# Do nothing for all targets but last. Also quiet the message "Noting to be done on xxx"
$(filter-out $(lastword $(EXTERNAL_TARGETS)), $(EXTERNAL_TARGETS)):
@cd .
cmake: $(BUILD_FOLDER)
cd $(BUILD_FOLDER) && cmake ..
$(BUILD_FOLDER):
mkdir $(BUILD_FOLDER)
.PHONY: cmake

476
HACKING
View file

@ -1,169 +1,383 @@
This file is licensed under the terms of the expat license, see the file EXPAT.
====== Midori - Contribute ======
+ Hacking guide for Midori +
**This document is licensed under the LGPL 2.1.**
- How to contribute
- Coding style
- Source files in the project
- Examplary source code
====== Check out the sources ======
+++ How to contribute +++
bzr branch lp:midori
There are several ways to contribute to the project:
The development **trunk** (master, tip) is the latest iteration of the next release. Browse it online and look for other branches at http://code.launchpad.net/midori or http://bazaar.launchpad.net/~midori/midori/trunk/tarball download a tarball of the latest revision.
For translating, have a look at the file TRANSLATE.
//The code used to be hosted in git.xfce.org/apps/midori.//
For helping with testing and triaging bug reports, you should register with the bug tracker at https://bugs.launchpad.net/midori and join #midori on irc.freenode.net where a lot of problems are discussed. You can start right away by trying to reproduce bug reports and comment with your findings.
Keep your copy updated:
If you are interested in contributing code, there are a few options. You can join #midori to discuss a particular problem you would like to look into, or a feature you would want to implement. Opening a bug report or feature request if there isn't already one is the next step. To attract some attention, if you attached a patch or have questions, ask in #midori.
bzr merge --pull
====== Join IRC chat rooms ======
+++ Coding style +++
Join irc://irc.freenode.net/midori #midori on Freenode https://kiwiirc.com/client/irc.freenode.net/midori or use webchat to talk about Midori, discuss bugs and new ideas.
====== Contribute other than touching code ======
Indentation is 4 spaces, no tabs, preferrably at 80 to 120 columns per line to
avoid automated line-breaks. Trailing whitespace is not desirable.
* http://bugs.launchpad.net/midori Go through problem reports and check Unconfirmed bugs or those lacking information and mark any duplicates you spot
* https://www.bountysource.com/#trackers/130181-midori Add a bounty for a feature or bug you'd like to support
* https://translations.launchpad.net/midori/trunk/+pots/trunk Translate to your own language
* https://github.com/eustasy/midori-browser.org/issues Report website bugs
* Write http://wiki.xfce.org/midori/tutorial your own extension - granted that's code, too, but maybe a little easier than hacking the core.
Declarations go to the beginning of a block, not inline. Variables of one plain
type may be grouped in one declaration, pointer types may not be grouped. The
asterisk goes next to the type.
Variables should be ordered in the order they are used.
====== Documentation resources ======
Comments explain functionality, they should not state facts. The appropriate
style is always C style /* */, not C++ style.
* https://wiki.gnome.org/Projects/Vala/Tutorial Vala Tutorial
* http://midori-browser.org/docs/api/vala/midori/ Midori Vala Docs
* http://midori-browser.org/docs/api/c/html/ Midori C Docs
Variable and function names should be animal, animal_shelter or animalsc in
case it would otherwise be very long. Loop counters may be single letters.
Type names should be Animal, AnimalShelter or AnimalSC. No prefixes from third
party projects should be used, such as GTK or WebKit, while underlines may be
used but do not have a particular meaning.
====== Build the code ======
There is a space between functions or keywords and round brackets, and curly
brackets always go on separate lines, at the indentation level of the
function, conditional or loop expression. Curly brackets are left out for single
statement conditionals and loops unless they notably help readability.
The type of a function goes on a separate line before the function.
Preprocessor instructions are indented with the code they relate to.
Code history is precious, so one should avoid renaming a lot of functions at
once or moving whole paragraphs only to add or remove a level of indentation.
Moving blocks of code around is also undesriable because it makes patches less
readable since the result looks like new code.
mkdir _build
cd _build
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
sudo make install
sudo gtk-update-icon-cache /usr/share/icons/hicolor
+++ Source files in the project +++
//Advanced Tip: Pass "-G Ninja" to cmake to use http://martine.github.io/ninja/ Ninja instead of make (usually packaged as ninja or ninja-build).//
Core:
Files prefixed with "midori-" in the folder "midori" make up the core. They
are essential to running the browser and mostly tailored to the browser.
All header files prefixed with "midori-" are considered supported API and
can be used to implement extensions.
"sokoke" is a collection of very specialized helper functions which change
from time to time as needed. In the past some of the code was moved to
"katze" when it was considered generally useful.
"socket" is a socket implementation with no dependency on other parts of
the core, which is used if Midori is built without an external single
instance support library.
Panels:
Files in the "panels" folder are classes that implement MidoriViewable and
which are loaded into the MidoriPanel at startup. These panels are in
principle optional.
Katze:
Re-usable classes and utility functions that don't depend on the core and
some of that code indeed found its way into other projects.
Extensions:
These are extensions, written in C, which are loaded optionally if the user
so chooses. Extensions can use API from "midori-" and "katze-" headers. Each
module consists of either a single .c file or a folder with .c files.
Tests:
Unit tests are run regularly to verify possible regressions or measure
performance of implementations. Except for select cases code changes should
not cause failure of unit tests.
If using GTK+3 you'll want to add -DUSE_GTK3=1 to the cmake command line.
You can build Midori using another C Compiler for example Clang. Just
add -DCMAKE_C_COMPILER=/path/to/compiler to the cmake arguments.
Then you can build following your normal procedure. Like this:
cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_C_COMPILER=/usr/bin/clang ..
make
Midori can be **run without being installed**.
_build/midori/midori
You can use a **temporary folder for testing** without affecting normal settings
_build/midori/midori -c /tmp/midoridev
You'll want to **unit test** the code if you're testing a new version or contributed your own changes:
xvfb-run make check
Automated daily builds in Launchpad (https://launchpad.net/~elementary-os/+archive/daily ppa:elementary-os/daily and https://launchpad.net/~midori/+archive/midori-dev ppa:midori/midori-dev) run these tests as well.
====== Debugging issues ======
Testing an installed release may reveal crashers or memory corruption which require investigating from a local build and obtaining a stacktrace (backtrace, crash log).
_build/midori/midori -g [OPTIONAL ARGUMENTS]
If the problem is a warning, not a crash GLib has a handy feature
env G_DEBUG=all _build/midori/midori -g
For more specific debugging output, depending on the feature in question you may use
env MIDORI_DEBUG=help _build/midori/midori
Whilst -g is convenient you may want to use proper gdb:
gdb
file _build/midori/midori
run
bt
On Windows you can open the folder where Midori is installed and double-click gdb.exe. A black command line window should appear.
file midori.exe
run
bt
To verify a regression you might need to revert a particular change:
+++ Examplary source code +++
# Revert only r6304
bzr merge . -r 6304..6303
/*
Copyright
LICENSE TEXT
*/
====== Coding style and quality ======
#include "foo.h"
Midori code should in general have:
#include "bar.h"
* 4 space indentation, no tabs
* Between 80 to 120 columns
* Prefer /* */ style comments
* Call variables //animal// and //animal_shelter// instead of <del>camelCase</del>
* Keep a space between functions/ keywords and round parentheses
#include <glib.h>
For Vala:
void
foobar (FooEnum bar, const gchar* foo)
{
gint n, i;
* Prefer //new Gtk.Widget ()// over //using Gtk; new Widget ()//
* Stick to standard Vala-style curly parentheses on the same line
* Cuddled //} else {// and //} catch (Error error) {//
if (!foo)
return;
For C:
#ifdef BAR_STRICT
if (bar == FOO_N)
{
g_print ("illegal value for 'bar'.\n");
return;
}
#endif
* Always keep { and } on their own line
/* this is an example */
switch (bar)
{
case FOO_FOO:
n = bar + 1;
break;
case FOO_BAR:
n = bar + 10;
break;
default:
n = 1;
}
//Extensions may historically diverge from the standard styling on a case-by-case basis//
for (i = 0; i < n; i++)
{
g_print ("%s\n", foo);
}
}
====== Committing code ======
Header file example:
Tell Bazaar your name if you haven't yet
bzr whoami "Real Name <email@address>"
/*
Copyright
LICENSE TEXT
*/
See what you did so far
bzr diff
#ifndef __FOO_H__
#define __FOO_H__ 1
Get an overview of changed and new files
bzr status
#ifdef HAVE_BAR_H
#define BAR_STRICT
#endif
Add new files, move/ rename or delete
bzr add FILENAME
bzr mv OLDFILE NEWFILE
bzr rm FILENAME
/* Types */
Commit all current changes - Bazaar automatically picks up edited files. //If you're used to git, think of an implicit staging area.//
bzr commit -p
typedef enum
{
FOO_FOO,
FOO_BAR,
FOO_N
} FooEnum;
If you have one or more related bug reports you should pass them as arguments. Once these commits are merged the bug will automatically be closed and the commit log shows clickable links to the reports.
bzr commit -p --fixes=lp:1111999
typedef struct
{
FooEnum foo_bar;
} FooStruct;
If you've done several commits
bzr log | less
bzr log -p | less
/* Declarations */
In the case you committed something wrong or want to ammend it:
bzr uncommit
void
foo_bar (FooEnum bar,
const gchar* foo);
If you end up with unrelated debugging code or other patches in the current changes, it's sometimes handy to temporarily clean up. //This may be seen as bzr's version of git stash.//
bzr shelve
bzr commit -p
bzr unshelve
const gchar*
foo_foo (FooStruct foo_struct,
guint number,
gboolean flag);
Remember to keep your branch updated:
bzr merge --pull
#endif /* !__FOO_H__ */
As a general rule of thumb, ''bzr help COMMAND'' gives you an explanation of any command and ''bzr help commands'' lists all available commands.
//If you're a die-hard git user, http://zyga.github.io/git-lp/ checkout git-lp to use git commands with the Bazaar repository.//
====== Push proposed changes ======
If you haven't yet, https://launchpad.net/~/+editsshkeys check that Launchpad has your SSH key - you can create an SSH key with **Passwords and Keys** aka **Seahorse** or ''ssh-keygen -t rsa'' - and use ''bzr launchpad-login'' to make youself known to bzr locally.
If you checked out trunk, and added your patch(es), just **push it under your username** in Launchpad and you can **propose it for merging into trunk**. This will automatically request a **review from other developers** who can then comment on it and provide feedback.
bzr push --remember lp:~USERNAME/midori/fix-bug1120383 && bzr lp-propose-merge lp:midori
lp-propose-merge command may not be working on some distributions like Arch or Fedora.
In case you get error like //bzr: ERROR: Unable to import library "launchpadlib": No module named launchpadlib// just use Launchpad's Web UI to propose a merge.
**What happens to all the branches?**
Leave the branches alone, **approved branches are cleared automatically** by Launchpad.
For larger feature branches, **use the team** in Launchpad to allow other developers to work on the code with you.
bzr push --remember lp:~midori/midori/featuritis && bzr lp-propose-merge lp:midori
What if I want to help out on an **existing merge request** that I can't push to?
bzr branch ~OTHERPERSON/midori/fix-bug1120383
cd fix-bug1120383
# make commits
bzr push lp:USERNAME~/midori/fix-bug1120383
bzr lp-propose-merge ~OTHERPERSON/midori/fix-bug1120383
Updating a branch that may be out of sync with trunk:
bzr pull
bzr: ERROR: These branches have diverged
bzr merge lp:midori
# Hand-edit conflicting changes
bzr resolve FILENAME
# If any conflicts remain continue fixing
bzr commit -m 'Merge lp:midori'
Save a little bandwidth, **branch from an existing local copy** that you keep around:
bzr branch lp:midori midori
bzr branch midori midori.fix-bug1120383
cd midori.fix-bug1120383
bzr pull lp:midori
====== Backwards compatibility ======
As of Midori 0.5.4 the formula is:
* Required dependencies need to be available on the previous stable https://apps.fedoraproject.org/packages/s/webkit Fedora and http://packages.ubuntu.com/search?suite=quantal&keywords=webkit&searchon=names Ubuntu
* For reference http://openports.se/www/webkit OpenBSD
* Windows XP through 8 are to date ABI compatible, all dependencies are included
^ package ^ F17 (2012-05-29) ^ U 12.10 (2012-10-18) ^
| glib2 | 2.32.4 | 2.34.0 |
| vala | 0.16.1 | 0.16 |
| gtk3 | 3.4.4 | 3.6.0 |
| gtk2 | 2.24.13 | 2.24.13 |
| soup | 2.38.1 | 2.40 |
| webkit | 1.8.3-1.fc17 | 1.10.0-0ubuntu1 |
====== Midori with(out) Granite ======
When built with Granite (-DUSE_GRANITE=1 or --enable-granite) there're a few key differences:
* Preferences uses a http://valadoc.elementaryos.org/Granite/Granite.Widgets.StaticNotebook.html Granite.Widgets.StaticNotebook
* URL completion styling is slightly different
* Clear Private Data uses **Granite.Widgets.LightWindow**
* Edit Bookmark and Security Details use http://valadoc.elementaryos.org/Granite/Granite.Widgets.PopOver.html Granite.Widgets.PopOver instead of Gtk.Window
====== Midori for Windows ======
===== For Linux developers =====
==== Dependencies ====
Midori for Windows is compiled on a Linux host and MinGW stack. For the current build Fedora 18 packages are used. Packages needed are listed below:
yum install gcc vala intltool
For a native build
yum install libsoup-devel webkitgtk3-devel sqlite-devel
For cross-compilation
yum install mingw{32,64}-webkitgtk3 mingw{32,64}-glib-networking mingw{32,64}-gdb mingw{32,64}-gstreamer-plugins-good
Packages needed when assembling the archive
yum install faenza-icon-theme p7zip mingw32-nsis greybird-gtk3-theme
Installing those should get you the packages needed to successfully build and develop Midori for Win32.
==== Building ====
For 32-bit builds:
mkdir _mingw32
cd _mingw32
mingw32-cmake .. -DUSE_ZEITGEIST=0 -DUSE_GTK3=1 -DCMAKE_INSTALL_PREFIX=/usr/i686-w64-mingw32/sys-root/mingw -DCMAKE_VERBOSE_MAKEFILE=0
make
sudo make install
For 64-bit builds:
mkdir _mingw64
cd _mingw64
mingw64-cmake .. -DUSE_ZEITGEIST=0 -DUSE_GTK3=1 -DCMAKE_INSTALL_PREFIX=/usr/x86_64-w64-mingw32/sys-root/mingw -DCMAKE_VERBOSE_MAKEFILE=0
make
sudo make install
Once built and tested you can assemble the Midori archive with a helper script
32-bit build:
env MINGW_PREFIX="/usr/i686-w64-mingw32/sys-root/mingw" ./win32/makedist/makedist.midori
64-bit build:
env MINGW_PREFIX="/usr/x86_64-w64-mingw32/sys-root/mingw/" ./win32/makedist/makedist.midori x64
===== Testing =====
For testing your changes unfortuantely a real system is needed because Midori and WebKitGTK+ don't work properly under Wine. Even if it works some problems are not visible when using Wine, but are present when running under a real Windows system and vice versa.
One way around it is to virtualize Windows on a Linux host and mount your MinGW directories as a network drive or shared folder.
===== For Windows developers =====
Rough list of prerequisites for building with MinGW on Windows
If in doubt whether to get 32 or 64 bit versions use 32 bit ones, they are more
universal and tend to be less broken.
==== MinGW compiler ====
Compiler should match the one that was used to build packages ideally.
* We will user *mingw64 rubenvb* release
* Lastest stable release is gcc 4.8.0
[[http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting
%20Win64/Personal%20Builds/rubenvb/gcc-4.8-release/|Releases]]
[[http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting
%20Win64/Personal%20Builds/rubenvb/gcc-4.8-release/x86_64-w64-mingw32-gcc-4.8.0-
win32_rubenvb.7z/download|Download]]
==== 7zip ====
We will need 7zip to extract various archives
http://www.7-zip.org/download.html Homepage
http://downloads.sourceforge.net/sevenzip/7z920.exe 32bit Installer
==== Python3 (to extract rpms) ====
We will need python3 to use download-mingw-rpm.py script.
If you don't plan to use it you can safely skip this step.
We get python3, whatever is the lastes stable release.
http://www.python.org/download/releases/3.3.5 Releases
http://www.python.org/downloads/release/python-335/ Download
http://www.python.org/ftp/python/3.3.5/python-3.3.5.amd64.msi Installer
Install Python and be sure to check "addd python.exe to path" installer checkbox.
==== download-mingw-rpm.py ====
We get download-mingw-rpm.py script from github. It uses Python3 and should fetch and
unpack rpm files for us.
[[https://github.com/mkbosmans/download-mingw-rpm/blob/master/download-
mingw-rpm.py|View Script]]
[[https://github.com/mkbosmans/download-mingw-rpm/raw/master/download-
mingw-rpm.py|Download Script]]
Usage:
* Launch cmd.exe
* Navigate to folder where the script was saved
* Make sure that python can access 7z.exe
* Run command and wait, it should extract the packages into your current directory
c:\Python33\python.exe download-mingw-rpm.py -u http://ftp.wsisiz.edu.pl/pub/linux/fedora/linux/updates/18/i386/ --deps mingw32-webkitgtk mingw32-glib-networking mingw32-gdb mingw32-gstreamer-plugins-good
[[http://dl.fedoraproject.org/pub/fedora/linux/releases/18/Everything/i386/os/Packages
/m/|Fedora 18 packages]]
The above URL for some reason does not work with the script.
==== MSYS ====
Msys contains shell and some small utilities
[[http://sourceforge.net/projects/mingw-w64/files/External%20binary
%20packages%20%28Win64%20hosted%29/MSYS%20%2832-bit%29/MSYS-20111123.zip/download|Download]]
==== CMake ====
http://www.cmake.org/cmake/resources/software.html Homepage
http://www.cmake.org/files/v2.8/cmake-2.8.12.2-win32-x86.exe Installer
When installing check the installer checkbox "add to path"
==== Bazaar ====
http://wiki.bazaar.canonical.com/WindowsDownloads Homepage
We will get 2.4 Stable Release (standalone)
http://launchpad.net/bzr/2.4/2.4.2/+download/bzr-2.4.2-1-setup.exe Installer
When installing check the installer checkbox "add to path"
==== Vala ====
http://ftp.gnome.org/pub/gnome/sources/vala/0.20/vala-0.20.0.tar.xz Source
==== Globbing it all together ====
Extracted rpms msys and mingw packages should form uniform unix like folder.
You use msys.bat to launch a shell
====== Jargon ======
* freeze: a period of bug fixes only eg. 4/2 cycle means 4 weeks of features and 2 weeks to focus on resolving existing problems
* MR: merge request, a branch proposed for review
* ninja: an internal tab, usually empty label, used for taking screenshots
* fortress: user of an ancient release like 0.4.3 as found on Raspberry Pie, Debian, Ubuntu
* katze, sokoke, tabby: API names and coincidentally cat breeds

82
INSTALL
View file

@ -1,82 +0,0 @@
This file is licensed under the terms of the expat license, see the file EXPAT.
+++ Installing Midori +++
Building and installing Midori is straightforward.
Make sure you have Python 2.4 or higher installed.
Change to the Midori folder on your hard disk in a terminal.
Run './waf configure'
Run './waf build'
You can now run Midori from the build folder like so
'_build/default/midori/midori'
Midori will pick up extensions and resources from the build folder;
it will NOT use localizations.
You can install it with './waf install'
If you need to do a clean rebuild, you can do either './waf clean'
in order to remove binaries or './waf distclean' which deletes generated
configuration files as well.
For further options run './waf --help'
+++ Debugging Midori +++
Midori is by default built with debugging symbols, make sure you have
installed 'gdb', the GNU Debugger.
It's a good idea to execute all unit test cases and see that they pass.
'xvfb-run ./waf check'
In this example, Xvfb is used to avoid relying on the local user setup.
You can also run Midori proper as 'gdb _build/default/midori/midori'.
Inside gdb, type 'run'.
Try to reproduce a crash that you experienced earlier,
this time Midori will freeze at the point of the crash.
Switch to your terminal, type bt ('backtrace') and hit Return.
What you obtained now is a backtrace that should include
function names and line numbers.
If the problem is a warning and not a crash, try this:
'G_DEBUG=all gdb _build/default/midori/midori'
If you are interested in HTTP communication, try this:
'MIDORI_DEBUG=headers _build/default/midori/midori'
Where 'headers' can be replaced with 'body' to get full message contents.
If you are interested in (non-) touchscreen behaviour, try this:
'MIDORI_TOUCHSCREEN=1 _build/default/midori/midori', or
'MIDORI_TOUCHSCREEN=0 _build/default/midori/midori'
If you want to "dry run" without WebKitGTK+ rendering, try this:
'MIDORI_DEBUG=unarmed _build/default/midori/midori'
If you want to test bookmarks, you can enable database tracing:
'MIDORI_DEBUG=bookmarks _build/default/midori/midori'
To disable Netscape plugins, use MOZ_PLUGIN_PATH=/.
When running from the build folder, extensions will also be located
in the build folder (setting MIDORI_EXTENSION_PATH is no longer needed).
For further information a tutorial for gdb and
reading up on how you can install debugging
symbols for libraries used by Midori are recommended.

31
README
View file

@ -1,22 +1,25 @@
This file is licensed under the terms of the expat license, see the file EXPAT.
Midori is a lightweight web browser.
Midori is a fast little WebKit browser with support for HTML5. It can manage
many open tabs and windows. The URL bar completes history, bookmarks, search
engines and open tabs out of the box. Web developers can use the powerful
web inspector that is a part of WebKit. Individual pages can easily be turned
into web apps and new profiles can be created on demand.
* Full integration with GTK+2 and GTK+3.
* Fast rendering with WebKit and HTML5 video with GStreamer.
* Tabs, windows and session management.
* History completion and configurable Web Search.
* User scripts and user styles support.
* Adblock Plus compatible, external download manager support.
* Straightforward bookmark management.
* Customizable interface, extensions written in C and Vala.
A number of extensions are included by default:
Requirements: GLib 2.22, GTK+ 2.16, WebkitGTK+ 1.1.17, libXML2,
libsoup 2.27.90, sqlite 3.0, Vala 0.14
* Adblock with support for ABP filter lists and custom rules is built-in.
* You can download files with Aria2 or SteadyFlow.
* User scripts and styles support a la Greasemonkey.
* Managing cookies and scripts via NoJS and Cookie Security Manager.
* Switching open tabs in a vertical panel or a popup window.
Optional: GTK+ 3.0, Unique 0.9, libnotify, gcr, Granite 0.2, WebKit2GTK+ 1.11.91/ 2.0.0
Requirements: GLib 2.32.3, GTK+ 2.24, WebkitGTK+ 1.8.1, libXML2,
libsoup 2.27.90, sqlite 3.0, Vala 0.16, libnotify
For installation instructions read INSTALL.
Optional: GTK+ 3.0, gcr, Granite 0.2, WebKit2GTK+ 1.11.91/ 2.0.0
For installation instructions read the file HACKING.
Please report comments, suggestions and bugs to:
https://bugs.launchpad.net/midori
@ -24,4 +27,4 @@ Please report comments, suggestions and bugs to:
And join the IRC channel #midori on irc.freenode.net
Check for new versions at:
http://www.twotoasts.de
http://www.midori-browser.org

55
cmake/ContainTest.cmake Normal file
View file

@ -0,0 +1,55 @@
# Copyright (C) 2013 Christian Dywan <christian@twotoasts.de>
include(ParseArguments)
macro(contain_test test_name executable)
parse_arguments(ARGS "test_name;executable" "" ${ARGN})
set(TEST_ENV "")
foreach(VARIABLE XDG_CACHE_HOME XDG_CONFIG_HOME XDG_DATA_HOME XDG_RUNTIME_DIR TMPDIR)
set(CONTAINER "${CMAKE_CURRENT_BINARY_DIR}/${test_name}-folders/${VARIABLE}")
set(TEST_ENV "${TEST_ENV}${VARIABLE}=${CONTAINER};")
endforeach()
add_dependencies(check contain-${test_name})
set_tests_properties(${test_name} PROPERTIES
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
TIMEOUT 42
ENVIRONMENT "${TEST_ENV}"
)
add_custom_target("contain-${test_name}"
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_CURRENT_BINARY_DIR}/${test_name}-folders
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${test_name}-folders/XDG_CACHE_HOME
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${test_name}-folders/XDG_CONFIG_HOME
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${test_name}-folders/XDG_DATA_HOME
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${test_name}-folders/XDG_RUNTIME_DIR
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${test_name}-folders/TMPDIR
)
string(REPLACE ${executable} ";" " " executable)
add_custom_target("gdb-${test_name}"
COMMAND env ${TEST_ENV} gdb
--batch -ex 'set print thread-events off'
-ex 'run' -ex 'bt'
--args ${executable}
DEPENDS "contain-${test_name}"
)
add_custom_target("valgrind-${test_name}"
COMMAND env ${TEST_ENV} valgrind
-q --leak-check=no --num-callers=4
--show-possibly-lost=no
--undef-value-errors=yes
--track-origins=yes
${executable}
DEPENDS "contain-${test_name}"
)
add_custom_target("callgrind-${test_name}"
COMMAND env ${TEST_ENV} valgrind
--tool=callgrind
--callgrind-out-file=${UNIT}.callgrind
${executable}
DEPENDS "contain-${test_name}"
)
endmacro(contain_test)

19
cmake/FindConvert.cmake Normal file
View file

@ -0,0 +1,19 @@
# Copyright (C) 2013 Christian Dywan
# Copyright (C) 2013 Olivier Duchateau
find_program (RSVG_CONVERT rsvg-convert)
if (RSVG_CONVERT)
set (CONVERT_FOUND TRUE)
macro (SVG2PNG filename install_destination)
string(REPLACE "/" "_" target ${filename})
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${filename}")
add_custom_target ("${target}.png" ALL
${RSVG_CONVERT} --keep-aspect-ratio --format=png "${CMAKE_CURRENT_SOURCE_DIR}/${filename}.svg"
--output "${CMAKE_CURRENT_BINARY_DIR}/${filename}.png"
)
install (FILES "${CMAKE_CURRENT_BINARY_DIR}/${filename}.png"
DESTINATION ${install_destination})
endmacro (SVG2PNG filename)
endif ()

38
cmake/FindIntltool.cmake Normal file
View file

@ -0,0 +1,38 @@
# FindIntltool.cmake
#
# Jim Nelson <jim@yorba.org>
# Copyright 2012-2013 Yorba Foundation
# Copyright (C) 2013 Christian Dywan
find_program (INTLTOOL_MERGE_EXECUTABLE intltool-merge)
find_program (INTLTOOL_UPDATE_EXECUTABLE intltool-update)
if (INTLTOOL_MERGE_EXECUTABLE)
set (INTLTOOL_MERGE_FOUND TRUE)
macro (INTLTOOL_MERGE_DESKTOP desktop_id po_dir)
add_custom_target ("${desktop_id}.desktop" ALL
${INTLTOOL_MERGE_EXECUTABLE} --desktop-style ${CMAKE_SOURCE_DIR}/${po_dir}
${CMAKE_CURRENT_SOURCE_DIR}/${desktop_id}.desktop.in ${desktop_id}.desktop
)
install (FILES "${CMAKE_CURRENT_BINARY_DIR}/${desktop_id}.desktop"
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/applications")
endmacro (INTLTOOL_MERGE_DESKTOP desktop_id po_dir)
macro (INTLTOOL_MERGE_APPDATA desktop_id po_dir)
add_custom_target ("${desktop_id}.appdata.xml" ALL
${INTLTOOL_MERGE_EXECUTABLE} --xml-style ${CMAKE_SOURCE_DIR}/${po_dir}
${CMAKE_CURRENT_SOURCE_DIR}/${desktop_id}.appdata.xml.in ${desktop_id}.appdata.xml
)
install (FILES "${CMAKE_CURRENT_BINARY_DIR}/${desktop_id}.appdata.xml"
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/appdata")
endmacro (INTLTOOL_MERGE_APPDATA desktop_id po_dir)
endif ()
if (INTLTOOL_UPDATE_EXECUTABLE)
set (INTLTOOL_UPDATE_FOUND TRUE)
add_custom_target (pot
COMMAND ${INTLTOOL_UPDATE_EXECUTABLE} "-p" "-g" ${GETTEXT_PACKAGE}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/po"
)
endif ()

18
cmake/FindVala.cmake Normal file
View file

@ -0,0 +1,18 @@
# Copyright (C) 2013 Christian Dywan <christian@twotoasts.de>
find_program(VALA_EXECUTABLE NAMES $ENV{VALAC} valac)
if (VALA_EXECUTABLE)
execute_process(COMMAND ${VALA_EXECUTABLE} "--version" OUTPUT_VARIABLE "VALA_VERSION")
string(REPLACE "Vala " "" VALA_VERSION ${VALA_VERSION})
string(STRIP ${VALA_VERSION} VALA_VERSION)
else ()
message(FATAL_ERROR "valac not found - install Vala compiler or specify compiler name eg. VALAC=valac-0.20")
endif ()
macro(vala_require VALA_REQUIRED)
if (${VALA_VERSION} VERSION_GREATER ${VALA_REQUIRED} OR ${VALA_VERSION} VERSION_EQUAL ${VALA_REQUIRED})
message(STATUS "valac ${VALA_VERSION} found")
else ()
message(FATAL_ERROR "valac >= ${VALA_REQUIRED} or later required")
endif ()
endmacro(vala_require)

38
cmake/GIR.cmake Normal file
View file

@ -0,0 +1,38 @@
# GIR.cmake
#
# Macros for building Gobject Introspection bindings for Midori API
find_program (GIR_SCANNER_BIN g-ir-scanner)
find_program (GIR_COMPILER_BIN g-ir-compiler)
if (GIR_SCANNER_BIN AND GIR_COMPILER_BIN)
set (GIR_FOUND TRUE)
set (GIR_VERSION "${MIDORI_MAJOR_VERSION}.${MIDORI_MINOR_VERSION}")
macro (gir_build module namespace)
add_custom_target ("g-ir-scanner_${module}" ALL
${GIR_SCANNER_BIN} -Imidori -I${CMAKE_SOURCE_DIR}/ -I${CMAKE_BINARY_DIR}/midori -I${CMAKE_SOURCE_DIR}/${module} -I${CMAKE_SOURCE_DIR}/toolbars -I.
--header-only -n ${namespace} --identifier-prefix ${namespace}
${CMAKE_SOURCE_DIR}/${module}/${module}-*.c ${CMAKE_SOURCE_DIR}/${module}/${module}-*.h
--pkg gtk+-2.0 --pkg webkit-1.0 --pkg gio-2.0 --pkg gobject-2.0
--warn-all -iGObject-2.0 -iGLib-2.0 -iGtk-2.0
--nsversion ${GIR_VERSION}
-o ${CMAKE_CURRENT_BINARY_DIR}/${namespace}-${GIR_VERSION}.gir
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
DEPENDS ${CMAKE_PROJECT_NAME})
add_custom_target ("g-ir-compiler_${module}" ALL
${GIR_COMPILER_BIN} ${CMAKE_CURRENT_BINARY_DIR}/${namespace}-${GIR_VERSION}.gir
--output ${CMAKE_CURRENT_BINARY_DIR}/${namespace}-${GIR_VERSION}.typelib
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
DEPENDS g-ir-scanner_${module})
endmacro (gir_build module namespace)
macro (gir module namespace)
gir_build (${module} ${namespace})
install (FILES "${CMAKE_CURRENT_BINARY_DIR}/${namespace}-${GIR_VERSION}.gir"
DESTINATION "${CMAKE_INSTALL_DATADIR}/gir-1.0/")
install (FILES "${CMAKE_CURRENT_BINARY_DIR}/${namespace}-${GIR_VERSION}.typelib"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/girepository-1.0/")
endmacro (gir module)
endif ()

71
cmake/GLibHelpers.cmake Normal file
View file

@ -0,0 +1,71 @@
# Copyright (C) 2010 David Sansome <me@davidsansome.com>
cmake_minimum_required(VERSION 2.6)
if(POLICY CMP0011)
cmake_policy(SET CMP0011 NEW)
endif(POLICY CMP0011)
find_program(GLIB_MKENUMS glib-mkenums)
find_program(GLIB_GENMARSHAL glib-genmarshal)
macro(add_glib_marshal outfiles name prefix otherinclude)
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${name}.h"
COMMAND ${GLIB_GENMARSHAL} --header "--prefix=${prefix}"
"${CMAKE_CURRENT_SOURCE_DIR}/${name}.list"
> "${CMAKE_CURRENT_BINARY_DIR}/${name}.h"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${name}.list"
)
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${name}.c"
COMMAND echo "\\#include \\\"${otherinclude}\\\"" > "${CMAKE_CURRENT_BINARY_DIR}/${name}.c"
COMMAND echo "\\#include \\\"glib-object.h\\\"" >> "${CMAKE_CURRENT_BINARY_DIR}/${name}.c"
COMMAND echo "\\#include \\\"${name}.h\\\"" >> "${CMAKE_CURRENT_BINARY_DIR}/${name}.c"
COMMAND ${GLIB_GENMARSHAL} --body "--prefix=${prefix}"
"${CMAKE_CURRENT_SOURCE_DIR}/${name}.list"
>> "${CMAKE_CURRENT_BINARY_DIR}/${name}.c"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${name}.list"
"${CMAKE_CURRENT_BINARY_DIR}/${name}.h"
)
list(APPEND ${outfiles} "${CMAKE_CURRENT_BINARY_DIR}/${name}.c")
endmacro(add_glib_marshal)
macro(add_glib_enumtypes_t outfiles name htemplate ctemplate)
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${name}.h"
COMMAND ${GLIB_MKENUMS}
--template "${htemplate}"
${ARGN} > "${CMAKE_CURRENT_BINARY_DIR}/${name}.h"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${ARGN} "${htemplate}"
)
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${name}.c"
COMMAND ${GLIB_MKENUMS}
--template "${ctemplate}"
${ARGN} > "${CMAKE_CURRENT_BINARY_DIR}/${name}.c"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${ARGN} ${ctemplate}
"${CMAKE_CURRENT_BINARY_DIR}/${name}.h"
)
list(APPEND ${outfiles} "${CMAKE_CURRENT_BINARY_DIR}/${name}.c")
endmacro(add_glib_enumtypes_t)
macro(add_glib_enumtypes outfiles name includeguard)
set(htemplate "${CMAKE_CURRENT_BINARY_DIR}/${name}.h.template")
set(ctemplate "${CMAKE_CURRENT_BINARY_DIR}/${name}.c.template")
# Write the .h template
add_custom_command(
OUTPUT ${htemplate} ${ctemplate}
COMMAND ${CMAKE_COMMAND}
"-Dctemplate=${ctemplate}"
"-Dhtemplate=${htemplate}"
"-Dname=${name}"
"-Dincludeguard=${includeguard}"
"\"-Dheaders=${ARGN}\""
-P "${CMAKE_SOURCE_DIR}/CMake/MakeGLibEnumTemplates.cmake"
DEPENDS "${CMAKE_SOURCE_DIR}/CMake/MakeGLibEnumTemplates.cmake" ${headers}
)
add_glib_enumtypes_t(${outfiles} ${name} ${htemplate} ${ctemplate} ${ARGN})
endmacro(add_glib_enumtypes)

61
cmake/GtkDoc.cmake Normal file
View file

@ -0,0 +1,61 @@
# GtkDoc.cmake
#
# Macros for building Midori API documentation.
# Copyright (C) 2013 Olivier Duchateau
find_program (GTKDOC_SCAN_BIN gtkdoc-scan)
find_program (GTKDOC_MKDB_BIN gtkdoc-mkdb)
find_program (GTKDOC_MKHTML_BIN gtkdoc-mkhtml)
find_program (GTKDOC_MKTMPL_BIN gtkdoc-mktmpl)
if (GTKDOC_SCAN_BIN AND GTKDOC_MKTMPL_BIN AND GTKDOC_MKDB_BIN
AND GTKDOC_MKHTML_BIN)
set (GTKDOC_FOUND TRUE)
macro (gtkdoc_build module)
message("gtkdoc: module ${module}")
# file (MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${module}")
add_custom_target ("gtkdoc-scan_${module}" ALL
${GTKDOC_SCAN_BIN} --module=${module}
--source-dir="${CMAKE_SOURCE_DIR}/${module}"
--output-dir="${CMAKE_CURRENT_BINARY_DIR}/${module}"
--rebuild-sections --rebuild-types
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
add_custom_target ("gtkdoc-tmpl_${module}" ALL
${GTKDOC_MKTMPL_BIN} --module=${module}
--output-dir="${CMAKE_CURRENT_BINARY_DIR}"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${module}"
DEPENDS "gtkdoc-scan_${module}")
add_custom_target ("gtkdoc-docbook_${module}" ALL
${GTKDOC_MKDB_BIN} --module=${module}
--output-dir="xml"
--source-dir="${CMAKE_SOURCE_DIR}/${module}"
--source-suffixes=c,h --output-format=xml
--default-includes=${module}/${module}.h
--sgml-mode --main-sgml-file=${module}.sgml
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${module}"
DEPENDS "gtkdoc-tmpl_${module}")
# Keep this target alone, otherwise build fails
add_custom_target ("gtkdoc-html_${module}" ALL
${GTKDOC_MKHTML_BIN} ${module}
"${CMAKE_CURRENT_BINARY_DIR}/${module}/${module}.sgml"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${module}/html"
DEPENDS "gtkdoc-docbook_${module}")
endmacro (gtkdoc_build module)
macro (gtkdoc module)
file (MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${module}/html")
gtkdoc_build (${module})
set (DOC_DIR "html/midori-${MIDORI_MAJOR_VERSION}-${MIDORI_MINOR_VERSION}")
install (DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${module}/html/"
DESTINATION "${CMAKE_INSTALL_DATADIR}/gtk-doc/${DOC_DIR}/${module}"
PATTERN "html/*"
PATTERN "index.sgml" EXCLUDE)
endmacro (gtkdoc module)
endif ()

View file

@ -0,0 +1,36 @@
##
# This is a helper Macro to parse optional arguments in Macros/Functions
# It has been taken from the public CMake wiki.
# See http://www.cmake.org/Wiki/CMakeMacroParseArguments for documentation and
# licensing.
##
macro(parse_arguments prefix arg_names option_names)
set(DEFAULT_ARGS)
foreach(arg_name ${arg_names})
set(${prefix}_${arg_name})
endforeach(arg_name)
foreach(option ${option_names})
set(${prefix}_${option} FALSE)
endforeach(option)
set(current_arg_name DEFAULT_ARGS)
set(current_arg_list)
foreach(arg ${ARGN})
set(larg_names ${arg_names})
list(FIND larg_names "${arg}" is_arg_name)
if(is_arg_name GREATER -1)
set(${prefix}_${current_arg_name} ${current_arg_list})
set(current_arg_name ${arg})
set(current_arg_list)
else(is_arg_name GREATER -1)
set(loption_names ${option_names})
list(FIND loption_names "${arg}" is_option)
if(is_option GREATER -1)
set(${prefix}_${arg} TRUE)
else(is_option GREATER -1)
set(current_arg_list ${current_arg_list} ${arg})
endif(is_option GREATER -1)
endif(is_arg_name GREATER -1)
endforeach(arg)
set(${prefix}_${current_arg_name} ${current_arg_list})
endmacro(parse_arguments)

236
cmake/ValaPrecompile.cmake Normal file
View file

@ -0,0 +1,236 @@
##
# Copyright 2009-2010 Jakob Westhoff. All rights reserved.
# Copyright 2012 elementary.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY JAKOB WESTHOFF ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
# EVENT SHALL JAKOB WESTHOFF OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and documentation are those
# of the authors and should not be interpreted as representing official policies,
# either expressed or implied, of Jakob Westhoff
##
include(ParseArguments)
find_package(Vala REQUIRED)
##
# Compile vala files to their c equivalents for further processing.
#
# The "vala_precompile" macro takes care of calling the valac executable on the
# given source to produce c files which can then be processed further using
# default cmake functions.
#
# The first parameter provided is a variable, which will be filled with a list
# of c files outputted by the vala compiler. This list can than be used in
# conjuction with functions like "add_executable" or others to create the
# neccessary compile rules with CMake.
#
# The initial variable is followed by a list of .vala files to be compiled.
# Please take care to add every vala file belonging to the currently compiled
# project or library as Vala will otherwise not be able to resolve all
# dependencies.
#
# The following sections may be specified afterwards to provide certain options
# to the vala compiler:
#
# PACKAGES
# A list of vala packages/libraries to be used during the compile cycle. The
# package names are exactly the same, as they would be passed to the valac
# "--pkg=" option.
#
# OPTIONS
# A list of optional options to be passed to the valac executable. This can be
# used to pass "--thread" for example to enable multi-threading support.
#
# CUSTOM_VAPIS
# A list of custom vapi files to be included for compilation. This can be
# useful to include freshly created vala libraries without having to install
# them in the system.
#
# GENERATE_VAPI
# Pass all the needed flags to the compiler to create an internal vapi for
# the compiled library. The provided name will be used for this and a
# <provided_name>.vapi file will be created.
#
# GENERATE_HEADER
# Let the compiler generate a header file for the compiled code. There will
# be a header file as well as an internal header file being generated called
# <provided_name>.h and <provided_name>_internal.h
#
# GENERATE_GIR
# Have the compiler generate a GObject-Introspection repository file with
# name: <provided_name>.gir. This can be later used to create a binary typelib
# using the GI compiler.
#
# GENERATE_SYMBOLS
# Output a <provided_name>.symbols file containing all the exported symbols.
#
# The following call is a simple example to the vala_precompile macro showing
# an example to every of the optional sections:
#
# vala_precompile(VALA_C mytargetname
# source1.vala
# source2.vala
# source3.vala
# PACKAGES
# gtk+-2.0
# gio-1.0
# posix
# DIRECTORY
# gen
# OPTIONS
# --thread
# CUSTOM_VAPIS
# some_vapi.vapi
# GENERATE_VAPI
# myvapi
# GENERATE_HEADER
# myheader
# GENERATE_GIR
# mygir
# GENERATE_SYMBOLS
# mysymbols
# )
#
# Most important is the variable VALA_C which will contain all the generated c
# file names after the call.
##
macro(vala_precompile output target_name)
parse_arguments(ARGS "TARGET;PACKAGES;OPTIONS;DIRECTORY;GENERATE_GIR;GENERATE_SYMBOLS;GENERATE_HEADER;GENERATE_VAPI;CUSTOM_VAPIS" "" ${ARGN})
if(ARGS_DIRECTORY)
set(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${ARGS_DIRECTORY})
else(ARGS_DIRECTORY)
set(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
endif(ARGS_DIRECTORY)
include_directories(${DIRECTORY})
set(vala_pkg_opts "")
foreach(pkg ${ARGS_PACKAGES})
list(APPEND vala_pkg_opts "--pkg=${pkg}")
endforeach(pkg ${ARGS_PACKAGES})
set(in_files "")
set(out_files "")
set(out_files_display "")
set(${output} "")
foreach(src ${ARGS_DEFAULT_ARGS})
string(REGEX MATCH "^/" IS_MATCHED ${src})
if(${IS_MATCHED} MATCHES "/")
set(src_file_path ${src})
else()
set(src_file_path ${CMAKE_CURRENT_SOURCE_DIR}/${src})
endif()
list(APPEND in_files ${src_file_path})
string(REPLACE ".vala" ".c" src ${src})
string(REPLACE ".gs" ".c" src ${src})
if(${IS_MATCHED} MATCHES "/")
get_filename_component(VALA_FILE_NAME ${src} NAME)
set(out_file "${CMAKE_CURRENT_BINARY_DIR}/${VALA_FILE_NAME}")
list(APPEND out_files "${CMAKE_CURRENT_BINARY_DIR}/${VALA_FILE_NAME}")
else()
set(out_file "${DIRECTORY}/${src}")
list(APPEND out_files "${DIRECTORY}/${src}")
endif()
list(APPEND ${output} ${out_file})
list(APPEND out_files_display "${src}")
endforeach(src ${ARGS_DEFAULT_ARGS})
set(custom_vapi_arguments "")
if(ARGS_CUSTOM_VAPIS)
foreach(vapi ${ARGS_CUSTOM_VAPIS})
if(${vapi} MATCHES ${CMAKE_SOURCE_DIR} OR ${vapi} MATCHES ${CMAKE_BINARY_DIR})
list(APPEND custom_vapi_arguments ${vapi})
else (${vapi} MATCHES ${CMAKE_SOURCE_DIR} OR ${vapi} MATCHES ${CMAKE_BINARY_DIR})
list(APPEND custom_vapi_arguments ${CMAKE_CURRENT_SOURCE_DIR}/${vapi})
endif(${vapi} MATCHES ${CMAKE_SOURCE_DIR} OR ${vapi} MATCHES ${CMAKE_BINARY_DIR})
endforeach(vapi ${ARGS_CUSTOM_VAPIS})
endif(ARGS_CUSTOM_VAPIS)
set(vapi_arguments "")
if(ARGS_GENERATE_VAPI)
list(APPEND out_files "${DIRECTORY}/${ARGS_GENERATE_VAPI}.vapi")
list(APPEND out_files_display "${ARGS_GENERATE_VAPI}.vapi")
set(vapi_arguments "--library=${ARGS_GENERATE_VAPI}" "--vapi=${ARGS_GENERATE_VAPI}.vapi")
# Header and internal header is needed to generate internal vapi
if (NOT ARGS_GENERATE_HEADER)
set(ARGS_GENERATE_HEADER ${ARGS_GENERATE_VAPI})
endif(NOT ARGS_GENERATE_HEADER)
endif(ARGS_GENERATE_VAPI)
set(header_arguments "")
if(ARGS_GENERATE_HEADER)
list(APPEND out_files "${DIRECTORY}/${ARGS_GENERATE_HEADER}.h")
list(APPEND out_files_display "${ARGS_GENERATE_HEADER}.h")
list(APPEND header_arguments "--header=${ARGS_GENERATE_HEADER}.h")
endif(ARGS_GENERATE_HEADER)
set(gir_arguments "")
if(ARGS_GENERATE_GIR)
list(APPEND out_files "${DIRECTORY}/${ARGS_GENERATE_GIR}.gir")
list(APPEND out_files_display "${ARGS_GENERATE_GIR}.gir")
set(gir_arguments "--gir=${ARGS_GENERATE_GIR}.gir")
endif(ARGS_GENERATE_GIR)
set(symbols_arguments "")
if(ARGS_GENERATE_SYMBOLS)
list(APPEND out_files "${DIRECTORY}/${ARGS_GENERATE_SYMBOLS}.symbols")
list(APPEND out_files_display "${ARGS_GENERATE_SYMBOLS}.symbols")
set(symbols_arguments "--symbols=${ARGS_GENERATE_SYMBOLS}.symbols")
endif(ARGS_GENERATE_SYMBOLS)
# Workaround for a bug that would make valac run twice. This file is written
# after the vala compiler generates C source code.
set(OUTPUT_STAMP ${CMAKE_CURRENT_BINARY_DIR}/${target_name}_valac.stamp)
add_custom_command(
OUTPUT
${OUTPUT_STAMP}
COMMAND
${VALA_EXECUTABLE}
ARGS
"-C"
${header_arguments}
${vapi_arguments}
${gir_arguments}
${symbols_arguments}
"-b" ${CMAKE_CURRENT_SOURCE_DIR}
"-d" ${DIRECTORY}
${vala_pkg_opts}
${ARGS_OPTIONS}
${in_files}
${custom_vapi_arguments}
COMMAND
touch
ARGS
${OUTPUT_STAMP}
DEPENDS
${in_files}
${ARGS_CUSTOM_VAPIS}
COMMENT
"Generating ${out_files_display}"
)
# This command will be run twice for some reason (pass a non-empty string to COMMENT
# in order to see it). Since valac is not executed from here, this won't be a problem.
add_custom_command(OUTPUT ${out_files} DEPENDS ${OUTPUT_STAMP} COMMENT "")
endmacro(vala_precompile)

23
config/CMakeLists.txt Normal file
View file

@ -0,0 +1,23 @@
# Copyright (C) 2013 Olivier Duchateau
set (SYSCONFDIR ${CMAKE_INSTALL_FULL_SYSCONFDIR})
set (XDG_CONFIG_DIR "xdg/${CMAKE_PROJECT_NAME}")
file (GLOB_RECURSE CONFIG_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *)
list (REMOVE_ITEM CONFIG_FILES "CMakeLists.txt")
if (${CMAKE_INSTALL_PREFIX} STREQUAL "/usr")
set(CMAKE_INSTALL_SYSCONFDIR "/etc")
endif()
foreach (FILE ${CONFIG_FILES})
string (FIND ${FILE} "adblock" ADBLOCK_CONF)
if (ADBLOCK_CONF GREATER -1)
string (REPLACE "config" "" dirname ${FILE})
install (FILES ${FILE}
DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/${XDG_CONFIG_DIR}/${dirname}")
else ()
install (FILES ${FILE}
DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/${XDG_CONFIG_DIR}")
endif ()
endforeach ()

224
configure vendored
View file

@ -1,157 +1,83 @@
#! /bin/sh
# waf configure wrapper
# Fancy colors used to beautify the output a bit.
#
if [ "$NOCOLOR" ] ; then
NORMAL=""
BOLD=""
RED=""
YELLOW=""
GREEN=""
else
NORMAL="\033[0m"
BOLD="\033[1m"
RED="\033[91m"
YELLOW="\033[01;93m"
GREEN="\033[92m"
# Copyright (C) 2013 Christian Dywan <christian@twotoasts.de>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# See the file COPYING for the full license text.
#
#~ Usage:
#~ ./configure [OPTIONS]
#~ Options:
#~ --prefix=PREFIX Installation prefix
#~ --enable-gtk3 Use GTK+3
#~ --disable-zeitgeist Disable Zeitgeist history integration
#~ --enable-granite Fancy notebook and pop-overs
#~ --enable-apidocs API documentation
#~
#~ Environment:
#~ VALAC if defined the valac executable to use, for example valac-0.16
#
if [ -z `command -v cmake` ]; then
echo Fatal: cmake not installed
exit 1
fi
EXIT_SUCCESS=0
EXIT_FAILURE=1
EXIT_ERROR=2
EXIT_BUG=10
while [ $# != 0 ]; do
case $1 in
--enable-gtk3)
ARGS="$ARGS -DUSE_GTK3=1";;
--disable-zeitgeist)
ARGS="$ARGS -DUSE_ZEITGEIST=0";;
--enable-granite)
ARGS="$ARGS -DUSE_GRANITE=1";;
--enable-apidocs)
ARGS="$ARGS -DUSE_APIDOCS=1";;
--extra-warnings)
ARGS="$ARGS -DEXTRA_WARNINGS=1";;
--prefix=*)
ARGS="$ARGS -DCMAKE_INSTALL_PREFIX=${1#*=}";;
*)
grep -e '^#~' $0 | sed s/#~//
exit
esac
shift
done
CUR_DIR=$PWD
BUILD_DIR="_build"
#possible relative path
WORKINGDIR=`dirname $0`
cd $WORKINGDIR
#abs path
WORKINGDIR=`pwd`
cd $CUR_DIR
# Checks for Python interpreter. Honours $PYTHON if set. Stores path to
# interpreter in $PYTHON.
#
checkPython()
{
if [ -z "$PYTHON" ] ; then
PYTHON=`which python2 2>/dev/null`
fi
if [ -z "$PYTHON" ] ; then
PYTHON=`which python 2>/dev/null`
fi
printf "Checking for Python\t\t\t: "
if [ ! -x "$PYTHON" ] ; then
printf $RED"not found!"$NORMAL"\n"
echo "Please make sure that the Python interpreter is available in your PATH"
echo "or invoke configure using the PYTHON flag, e.g."
echo "$ PYTHON=/usr/local/bin/python configure"
exit $EXIT_FAILURE
fi
printf $GREEN"$PYTHON"$NORMAL"\n"
}
# Checks for WAF. Honours $WAF if set. Stores path to 'waf' in $WAF.
# Requires that $PYTHON is set.
#
checkWAF()
{
printf "Checking for WAF\t\t\t: "
#installed miniwaf in sourcedir
if [ -z "$WAF" ] ; then
if [ -f "${WORKINGDIR}/waf" ] ; then
WAF="${WORKINGDIR}/waf"
if [ ! -x "$WAF" ] ; then
chmod +x $WAF
fi
fi
fi
if [ -z "$WAF" ] ; then
if [ -f "${WORKINGDIR}/waf-light" ] ; then
${WORKINGDIR}/waf-light --make-waf
WAF="${WORKINGDIR}/waf"
fi
fi
#global installed waf with waf->waf.py link
if [ -z "$WAF" ] ; then
WAF=`which waf 2>/dev/null`
fi
# neither waf nor miniwaf could be found
if [ ! -x "$WAF" ] ; then
printf $RED"not found"$NORMAL"\n"
echo "Go to http://code.google.com/p/waf/"
echo "and download a waf version"
exit $EXIT_FAILURE
else
printf $GREEN"$WAF"$NORMAL"\n"
fi
WAF="$PYTHON $WAF"
}
# Generates a Makefile. Requires that $WAF is set.
#
generateMakefile()
{
cat > Makefile << EOF
#!/usr/bin/make -f
# Waf Makefile wrapper
WAF_HOME=$CUR_DIR
all:
@$WAF build
all-debug:
@$WAF -v build
all-progress:
@$WAF -p build
install:
@if test -n "\$(DESTDIR)"; then \\
$WAF install --destdir="\$(DESTDIR)"; \\
else \\
$WAF install; \\
fi;
.PHONY: install
uninstall:
@if test -n "\$(DESTDIR)"; then \\
$WAF uninstall --destdir="\$(DESTDIR)"; \\
else \\
$WAF uninstall; \\
fi;
clean:
@$WAF clean
distclean:
@$WAF distclean
@-rm -rf _build
@-rm -f Makefile
check:
@$WAF check
dist:
@$WAF dist
EOF
}
checkPython
checkWAF
echo "calling waf configure with parameters"
$WAF configure $* || exit $EXIT_ERROR
if [ -f "Makefile" ] ; then
echo ""
else
generateMakefile
if [ ! -f GNUmakefile ]; then
cp -v GNUmakefile.in GNUmakefile || exit 1
fi
exit $EXIT_SUCCESS
# cmake was invoked in toplevel folder before
# clean up cmake generated build files to prevent conflicts
if [ -f CMakeCache.txt ]; then
echo
echo '####################################################################################'
echo 'CMake build files detected in toplevel folder !!'
echo 'Please always run "cmake" command from distinct folder when you use cmake yourself.'
echo '####################################################################################'
echo
echo 'Cleaning up...'
echo
rm -fr $BUILD_DIR
rm CMakeCache.txt config.h Makefile
find . -iname CMakeFiles -type d|xargs rm -fr
find . -iname cmake_install.cmake -exec rm {} \;
find . -iname CTestTestfile.cmake -exec rm {} \;
find . -iname *-folders -type d|xargs rm -fr
rm -fr data/logo-shade
fi
mkdir -p $BUILD_DIR && cd $BUILD_DIR || exit 1
cmake $ARGS .. || exit 1
echo
echo "Configuring done, run \"make\" to compile"

58
data/CMakeLists.txt Normal file
View file

@ -0,0 +1,58 @@
# Copyright (C) 2013 Christian Dywan <christian@twotoasts.de>
include(FindConvert)
if (NOT CONVERT_FOUND)
message(FATAL_ERROR "rsvg-convert not found")
endif ()
include(FindIntltool)
if (NOT INTLTOOL_MERGE_FOUND)
message(FATAL_ERROR "intltool-merge not found")
elseif (NOT INTLTOOL_UPDATE_FOUND)
message(FATAL_ERROR "intltool-update not found")
endif ()
file(GLOB_RECURSE DATA_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *)
list(REMOVE_ITEM DATA_FILES "CMakeLists.txt")
foreach(FILE ${DATA_FILES})
if (${FILE} MATCHES "faq.")
install(FILES ${FILE} DESTINATION ${CMAKE_INSTALL_DOCDIR})
elseif (${FILE} MATCHES ".desktop")
if (NOT WIN32)
string(REPLACE ".desktop.in" "" DESKTOP_ID ${FILE})
INTLTOOL_MERGE_DESKTOP (${DESKTOP_ID} po)
endif ()
elseif (${FILE} MATCHES ".appdata.xml")
if (NOT WIN32)
string(REPLACE ".appdata.xml.in" "" DESKTOP_ID ${FILE})
INTLTOOL_MERGE_APPDATA (${DESKTOP_ID} po)
endif ()
elseif (${FILE} MATCHES "\\.svg$")
string(REPLACE ".svg" "" IMG_ID ${FILE})
string (FIND ${FILE} "/" IS_DIR)
if (IS_DIR GREATER -1)
string(REPLACE "/" ";" DIR_LIST ${FILE})
LIST(GET DIR_LIST 0 S_DIR)
SVG2PNG (${IMG_ID} "${CMAKE_INSTALL_DATADIR}/midori/res/${S_DIR}")
else ()
SVG2PNG (${IMG_ID} "${CMAKE_INSTALL_DATADIR}/midori/res/")
endif()
# These are being handled in add_executable for the "midori" binary
elseif (${FILE} MATCHES "\\.ico$")
elseif (${FILE} MATCHES "\\.rc$")
# This is only meant for testing, and not used in production
elseif (${FILE} MATCHES "\\.swf$")
else()
string(FIND ${FILE} "/" IS_DIR)
if (IS_DIR GREATER -1)
string(REPLACE "/" ";" DIR_LIST ${FILE})
LIST(GET DIR_LIST 0 S_DIR)
LIST(GET DIR_LIST 1 S_FILE)
install(FILES ${S_DIR}/${S_FILE} DESTINATION ${CMAKE_INSTALL_DATADIR}/midori/res/${S_DIR})
else ()
install(FILES ${FILE} DESTINATION ${CMAKE_INSTALL_DATADIR}/midori/res/)
endif()
endif()
endforeach()

View file

@ -3,25 +3,14 @@
This file is licensed under the terms of the expat license, see the file EXPAT.
*/
body {
background-color: #eee;
margin: 0;
padding: 0;
}
#container {
background: #f6fff3;
min-width: 70%;
max-width: 70%;
margin: 2em auto 1em;
padding: 1em;
border: 0.2em solid #9acb7f;
-webkit-border-radius: 1em;
}
#icon {
float: left;
padding-left: 1%;
padding-top: 1%;
background-color: #dedede;
color: #222222;
font-family: 'Open Sans', 'Droid Sans', Arial, sans-serif;
font-size: 14px;
font-style: normal;
font-variant: normal;
font-weight: normal;
margin-top: 100px;
}
html[dir="rtl"] #icon {
@ -29,24 +18,40 @@ html[dir="rtl"] #icon {
padding-right: 1%;
}
.indent {
margin-left: 5%;
}
#main {
float: right;
width: 75%;
max-width: 50%;
margin-left: auto;
margin-right: auto;
min-width: 480px;
background-repeat: no-repeat;
background-color: #ffffff;
border: 1px solid rgba(0, 0, 0, .3);
padding: 25px;
-webkit-border-radius: 4px;
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.1);
background-position-x: 22px;
background-position-y: 54px;
}
#text {
margin-left: 15%;
}
h1 {
font-size: 1.4em;
font-weight: bold;
white-space: nowrap;
font-family: 'Open Sans', 'Droid Sans', arial, sans-serif;
font-size: 32px;
font-style: normal;
font-variant: normal;
font-weight: 300;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
#logo {
position: absolute; bottom: 15px;
z-index: -1;
}
html[dir="ltr"] #logo {
right: 15px;
}
@ -61,11 +66,35 @@ button img {
padding: 2px 1px;
}
#message {
font-size: 1.1em;
word-wrap: break-word;
button {
font-size: 14px;
}
#description {
font-size: 1em;
.message {
overflow: hidden;
text-overflow: ellipsis;
}
.description {
font-size: 1em;
overflow: hidden;
text-overflow: ellipsis;
}
#suggestions {
overflow: hidden;
text-overflow: ellipsis;
}
#button {
text-align: right;
}
#logo {
position: absolute; bottom: 15px;
z-index: -1;
}
form {
margin-bottom: 0px;
}

55
data/adblock.list Normal file
View file

@ -0,0 +1,55 @@
[Adblock Plus 2.0]
! Version: 201402142200
! Title: Exercise
! Last modified: 11 Feb 2014 22:00 UTC
! Expires: 3 days (update frequency)
! Homepage: http://www.midori-browser.org
! Licence: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
! Copyright (C) 2014 Christian Dywan <christian@twotoasts.de>
!
! Some freeform text:
! Yadayada http://example.com/ e-mail (somebody@example.com).
!
!-----Spam eggs--------!
! *** la:le/lu_foo_bar.txt ***
|/http://ads.blub.boing/*$domain=xxx.com
|/http://ads.blub.boing/*$domain=xxx.com,foo.fr,coco.at
/market.php?$domain=adf.ly|foo.com
/?placement=$script,domain=putlocker.com|sockshare.com
! Some basic filters
*ads.foo.bar*
*ads.bogus.name*
||^http://ads.bla.blub/*
engine.adct.ru/*?
/addyn|*|adtech;
doubleclick.net/pfadx/*.mtvi
objects.tremormedia.com/embed/xml/*.xml?r=
videostrip.com^*/admatcherclient.
test.dom/test?var
/adpage.
br.gcl.ru/cgi-bin/br/
_300x600.
_rectangle_ads.
+adverts/
-2/ads/
! CSS elements
old.tv,delicio.us,longc.at###box
##.zRightAdNote
###advertisingModule160x600
##a[href$="/vghd.shtml"]
imagetwist.com###left[align="center"] > center > a[target="_blank"]
! Options
||videobox.com/?tid=$popup
||sexsearchcom.com^$popup,third-party
||206.217.206.137^$third-party
/spopunder^$popup
||putlocker.com^*.php?*title$subdocument
://ads.$popup
! Whitelist
@@||hortifor.com/images/*120x60$~third-party
@@||stickam.com/wb/www/category/300x250/$image
@@||adultadworld.com/adhandler/$subdocument

View file

@ -0,0 +1,35 @@
/*
Copyright (C) 2014 Alexander V. Butenko <a.butenka@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
function getElementsByAttribute (strTagName, strAttributeName, arrAttributeValue) {
var arrElements = document.getElementsByTagName (strTagName);
var arrReturnElements = new Array();
for (var j=0; j<arrAttributeValue.length; j++) {
var strAttributeValue = arrAttributeValue[j];
for (var i=0; i<arrElements.length; i++) {
var oCurrent = arrElements[i];
var oAttribute = oCurrent.getAttribute && oCurrent.getAttribute (strAttributeName);
if (oAttribute && oAttribute.length > 0 && strAttributeValue.indexOf (oAttribute) != -1)
arrReturnElements.push (oCurrent);
}
}
return arrReturnElements;
};
function hideElementBySrc (uris) {
var oElements = getElementsByAttribute('img', 'src', uris);
if (oElements.length == 0)
oElements = getElementsByAttribute ('iframe', 'src', uris);
for (var i=0; i<oElements.length; i++) {
oElements[i].style.visibility = 'hidden !important';
oElements[i].style.width = '0';
oElements[i].style.height = '0';
}
};

View file

@ -1,7 +1,15 @@
/**
* An autosuggest textbox control.
* from Nicholas C. Zakas (Author) example: http://www.nczonline.net/
* Adopted for Midori by Alexander V. Butenko <a.butenka@gmail.com>
/*
An autosuggest textbox control.
Copyright (C) 2012 Nicholas C. Zakas (Author) example: http://www.nczonline.net/
Adopted for Midori by Alexander V. Butenko <a.butenka@gmail.com>
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of the Nicholas C. Zakas nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
function AutoSuggestControl(oTextbox /*:HTMLInputElement*/,

91
data/bookmarks/Create.sql Normal file
View file

@ -0,0 +1,91 @@
CREATE TABLE IF NOT EXISTS bookmarks
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
parentid INTEGER DEFAULT NULL,
title TEXT,
uri TEXT,
desc TEXT,
app INTEGER,
toolbar INTEGER,
pos_panel INTEGER,
pos_bar INTEGER,
created DATE DEFAULT CURRENT_TIMESTAMP,
last_visit DATE,
visit_count INTEGER DEFAULT 0,
nick TEXT,
FOREIGN KEY(parentid) REFERENCES bookmarks(id) ON DELETE CASCADE
);
/* trigger: insert panel position */
CREATE TRIGGER IF NOT EXISTS bookmarkInsertPosPanel
AFTER INSERT ON bookmarks FOR EACH ROW
BEGIN UPDATE bookmarks SET pos_panel = (
SELECT ifnull(MAX(pos_panel),0)+1 FROM bookmarks WHERE
(NEW.parentid IS NOT NULL AND parentid = NEW.parentid)
OR (NEW.parentid IS NULL AND parentid IS NULL))
WHERE id = NEW.id; END;
/* trigger: insert Bookmarkbar position */
CREATE TRIGGER IF NOT EXISTS bookmarkInsertPosBar
AFTER INSERT ON bookmarks FOR EACH ROW WHEN NEW.toolbar=1
BEGIN UPDATE bookmarks SET pos_bar = (
SELECT ifnull(MAX(pos_bar),0)+1 FROM bookmarks WHERE
((NEW.parentid IS NOT NULL AND parentid = NEW.parentid)
OR (NEW.parentid IS NULL AND parentid IS NULL)) AND toolbar=1)
WHERE id = NEW.id; END;
/* trigger: update panel position */
CREATE TRIGGER IF NOT EXISTS bookmarkUpdatePosPanel
BEFORE UPDATE OF parentid ON bookmarks FOR EACH ROW
WHEN ((NEW.parentid IS NULL OR OLD.parentid IS NULL)
AND NEW.parentid IS NOT OLD.parentid) OR
((NEW.parentid IS NOT NULL AND OLD.parentid IS NOT NULL)
AND NEW.parentid!=OLD.parentid)
BEGIN UPDATE bookmarks SET pos_panel = pos_panel-1
WHERE ((OLD.parentid IS NOT NULL AND parentid = OLD.parentid)
OR (OLD.parentid IS NULL AND parentid IS NULL)) AND pos_panel > OLD.pos_panel;
UPDATE bookmarks SET pos_panel = (
SELECT ifnull(MAX(pos_panel),0)+1 FROM bookmarks
WHERE (NEW.parentid IS NOT NULL AND parentid = NEW.parentid)
OR (NEW.parentid IS NULL AND parentid IS NULL))
WHERE id = OLD.id; END;
/* trigger: update Bookmarkbar position */
CREATE TRIGGER IF NOT EXISTS bookmarkUpdatePosBar0
AFTER UPDATE OF parentid, toolbar ON bookmarks FOR EACH ROW
WHEN ((NEW.parentid IS NULL OR OLD.parentid IS NULL)
AND NEW.parentid IS NOT OLD.parentid)
OR ((NEW.parentid IS NOT NULL AND OLD.parentid IS NOT NULL)
AND NEW.parentid!=OLD.parentid) OR (OLD.toolbar=1 AND NEW.toolbar=0)
BEGIN UPDATE bookmarks SET pos_bar = NULL WHERE id = NEW.id;
UPDATE bookmarks SET pos_bar = pos_bar-1
WHERE ((OLD.parentid IS NOT NULL AND parentid = OLD.parentid)
OR (OLD.parentid IS NULL AND parentid IS NULL)) AND pos_bar > OLD.pos_bar; END;
/* trigger: update Bookmarkbar position */
CREATE TRIGGER IF NOT EXISTS bookmarkUpdatePosBar1
BEFORE UPDATE OF parentid, toolbar ON bookmarks FOR EACH ROW
WHEN ((NEW.parentid IS NULL OR OLD.parentid IS NULL)
AND NEW.parentid IS NOT OLD.parentid) OR
((NEW.parentid IS NOT NULL AND OLD.parentid IS NOT NULL)
AND NEW.parentid!=OLD.parentid) OR (OLD.toolbar=0 AND NEW.toolbar=1)
BEGIN UPDATE bookmarks SET pos_bar = (
SELECT ifnull(MAX(pos_bar),0)+1 FROM bookmarks WHERE
(NEW.parentid IS NOT NULL AND parentid = NEW.parentid)
OR (NEW.parentid IS NULL AND parentid IS NULL))
WHERE id = OLD.id; END;
/* trigger: delete panel position */
CREATE TRIGGER IF NOT EXISTS bookmarkDeletePosPanel
AFTER DELETE ON bookmarks FOR EACH ROW
BEGIN UPDATE bookmarks SET pos_panel = pos_panel-1
WHERE ((OLD.parentid IS NOT NULL AND parentid = OLD.parentid)
OR (OLD.parentid IS NULL AND parentid IS NULL)) AND pos_panel > OLD.pos_panel; END;
/* trigger: delete Bookmarkbar position */
CREATE TRIGGER IF NOT EXISTS bookmarkDeletePosBar
AFTER DELETE ON bookmarks FOR EACH ROW WHEN OLD.toolbar=1
BEGIN UPDATE bookmarks SET pos_bar = pos_bar-1
WHERE ((OLD.parentid IS NOT NULL AND parentid = OLD.parentid)
OR (OLD.parentid IS NULL AND parentid IS NULL)) AND pos_bar > OLD.pos_bar; END;

View file

@ -0,0 +1,6 @@
INSERT INTO main.bookmarks (parentid, title, uri, desc, app, toolbar)
SELECT NULL AS parentid, title, uri, desc, app, toolbar
FROM old_db.bookmarks;
UPDATE main.bookmarks SET parentid = (
SELECT id FROM main.bookmarks AS b1 WHERE b1.title = (
SELECT folder FROM old_db.bookmarks WHERE title = main.bookmarks.title));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,3 +0,0 @@
[D-BUS Service]
Name=com.nokia.midori
Exec=/usr/bin/midori

View file

@ -9,21 +9,22 @@
<link rel="stylesheet" type="text/css" href="res://about.css" />
</head>
<body>
<div id="container">
<img id="logo" src="res://logo-shade.png" />
<img id="icon" src="stock://gtk-dialog-error" />
<div id="main">
<img id="logo" src="res://logo-shade.png" />
<div id="main" style="background-image: url({error_icon});">
<div id="text">
<h1>{title}</h1>
<p id="message">{message}</p>
<p id="description">{description}</p>
<form method="GET" action="{uri}">
<button type="submit" onclick="location.reload(); return false;">
<p class="message">{message}<br><i>{description}</i></p>
{suggestions}
</div>
<form method="GET" action="{uri}" id="button">
<button type="submit" onclick="location.reload(); return false;" {autofocus}>
<img style="{hide-button-images}" src="stock://gtk-refresh"/>
<span>{tryagain}</span>
</button>
</form>
</div>
<br style="clear: both;"/>
</div>
</body>
<br style="clear: both;"/>
</div>
</body>
</html>

View file

@ -4,21 +4,20 @@
<meta charset="utf-8" />
<title>midori:faq</title>
<meta name="generator" content="DokuWiki"/>
<meta name="robots" content="noindex,nofollow"/>
<meta name="date" content="2013-05-13T12:47:33+0200"/>
<meta name="robots" content="index,follow"/>
<meta name="keywords" content="midori,faq"/>
<link rel="search" type="application/opensearchdescription+xml" href="/lib/exe/opensearch.php" title="Xfce Wiki"/>
<link rel="start" href="/"/>
<link rel="contents" href="/midori/faq?do=index" title="Sitemap"/>
<link rel="alternate" type="application/rss+xml" title="Recent Changes" href="/feed.php"/>
<link rel="alternate" type="application/rss+xml" title="Current Namespace" href="/feed.php?mode=list&amp;ns=midori"/>
<link rel="alternate" type="application/rss+xml" title="Recent changes" href="/feed.php"/>
<link rel="alternate" type="application/rss+xml" title="Current namespace" href="/feed.php?mode=list&amp;ns=midori"/>
<link rel="alternate" type="text/html" title="Plain HTML" href="/_export/xhtml/midori/faq"/>
<link rel="alternate" type="text/plain" title="Wiki Markup" href="/_export/raw/midori/faq"/>
<link rel="canonical" href="http://wiki.xfce.org/midori/faq"/>
<link rel="stylesheet" href="faq.css" />
<script type="text/javascript">/*<![CDATA[*/var NS='midori';var JSINFO = {"id":"midori:faq","namespace":"midori"};
/*!]]>*/</script>
<script type="text/javascript" charset="utf-8" src="/lib/exe/js.php?tseed=1358197876"></script>
<script type="text/javascript" charset="utf-8" src="/lib/exe/js.php?tseed=af5aadc3291d3baa2d8665b51a0cedf3"></script>
</head>
<body>
<div class="dokuwiki export">
@ -29,7 +28,7 @@
<ul class="toc">
<li class="level1"><div class="li"><a href="#midori_-_frequently_asked_questions">Midori - Frequently asked questions</a></div></li>
<li class="level1"><div class="li"><a href="#getting_started">Getting started</a></div></li>
<li class="level1"><div class="li"><a href="#about_midori">About Midori</a></div></li>
<li class="level1"><div class="li"><a href="#common_problems">Common problems</a></div>
<ul class="toc">
<li class="level2"><div class="li"><a href="#security_features">Security features</a></div></li>
@ -58,7 +57,14 @@
<li class="level1"><div class="li"><a href="#user_scripts_and_styles">User scripts and styles</a></div>
<ul class="toc">
<li class="level2"><div class="li"><a href="#user_styles">User styles</a></div></li>
</ul></li>
</ul>
</li>
<li class="level1"><div class="li"><a href="#midori_architecture">Midori Architecture</a></div>
<ul class="toc">
<li class="level2"><div class="li"><a href="#webkit_version_numbers">WebKit Version Numbers</a></div></li>
</ul>
</li>
<li class="level1"><div class="li"><a href="#midori_and_mediaherald">Midori and mediaHerald</a></div></li>
</ul>
</div>
</div>
@ -72,12 +78,12 @@
</p>
<p>
This is <a href="http://wiki.xfce.org/midori/faq">a snapshot of the online FAQ</a> about the Midori Web Browser. Anyone feel free to improve and/ or extend this page, but keep it clean and easy to read for other Xfce users.
This is <a href="http://wiki.xfce.org/midori/faq">a snapshot of the online FAQ</a> about the Midori Web Browser. Anyone should feel free to improve or extend this page, but keep it clean and easy to read for other users.
</p>
</div>
<!-- EDIT1 SECTION "Midori - Frequently asked questions" [1-289] -->
<h1 class="sectionedit2" id="getting_started">Getting started</h1>
<!-- EDIT1 SECTION "Midori - Frequently asked questions" [1-286] -->
<h1 class="sectionedit2" id="about_midori">About Midori</h1>
<div class="level1">
</div>
@ -86,7 +92,7 @@ This is <a href="http://wiki.xfce.org/midori/faq">a snapshot of the online FAQ</
<div class="level4">
<p>
Midori is a Web Browser, that aims to be lightweight and fast. It aligns well with the Xfce philosophy of making the most out of available resources.
Midori is a Web browser that aims to be lightweight and fast. It aligns well with the Xfce philosophy of making the most out of available resources. It has a customizable interface using the GTK+ toolkit.
</p>
</div>
@ -113,7 +119,7 @@ The paw of a green cat. Obviously. Also it resembles the letter “M” in “Mi
<div class="level4">
<p>
Midori is basically very portable and should run on all platforms that its dependencies support.
Midori is portable and should run on all platforms that its dependencies support. Releases exist on various Linux distributions, for Windows versions prior to 8.1 (for now), and BSD.
</p>
</div>
@ -126,7 +132,7 @@ Midori and all delivered artwork are licensed under the LGPL2.
</p>
</div>
<!-- EDIT2 SECTION "Getting started" [290-1188] -->
<!-- EDIT2 SECTION "About Midori" [287-1323] -->
<h1 class="sectionedit3" id="common_problems">Common problems</h1>
<div class="level1">
@ -136,19 +142,15 @@ Midori and all delivered artwork are licensed under the LGPL2.
<div class="level4">
<p>
Some websites discriminate against your browser.
Some websites give up if they don&#039;t recognize your browser as Chrome, Firefox, <abbr title="Internet Explorer">IE</abbr>, etc.
</p>
<p>
If you have Midori older than 0.3.5, go to Preferences &gt; Network &gt; Identify As, and choose Custom. Paste this into the entry:
You can change the browser name sent to web sites at Preferences &gt; Network &gt; Identify As
</p>
<p>
Mozilla/5.0 (X11; Linux) AppleWebKit/531.2+ Midori/0.3
</p>
<p>
If this doesn&#039;t do the trick, try choosing Safari or Firefox.
Either choose a predefined user-agent or choose Custom and find a suitable string in <a href="http://useragentstring.com/pages/useragentstring.php" class="urlextern" title="http://useragentstring.com/pages/useragentstring.php" rel="nofollow">a list</a>.
</p>
<p>
@ -156,7 +158,7 @@ Many other websites use similar means of detecting the browser.
</p>
<p>
Ideally Google would follow <a href="http://code.google.com/p/doctype/wiki/ArticleGoogleChromeCompatFAQ#UserAgent_Detection" class="urlextern" title="http://code.google.com/p/doctype/wiki/ArticleGoogleChromeCompatFAQ#UserAgent_Detection" rel="nofollow">their own recommendation</a> at some point.
Google <a href="http://web.archive.org/web/20100625211333/http://code.google.com/p/doctype/wiki/ArticleGoogleChromeCompatFAQ" class="urlextern" title="http://web.archive.org/web/20100625211333/http://code.google.com/p/doctype/wiki/ArticleGoogleChromeCompatFAQ" rel="nofollow">previously recommended</a> the superior practice for website creators of checking which features are present rather than browser name/version, but has since weakened this stance with their browser&#039;s growing market share.
</p>
</div>
@ -165,7 +167,28 @@ Ideally Google would follow <a href="http://code.google.com/p/doctype/wiki/Artic
<div class="level4">
<p>
The set of themed icons Midori can use is very limited. For instance icons for a new tab or the throbber are not guaranteed to be available. To fix this, install a Freedesktop.org <abbr title="specification">spec</abbr> compliant icon theme, such as Elementary, Faenza, Buuf or GNOME.
Midori uses a wide variety of icons which may not be present in all themes. For instance icons for a new tab, to represent scripts, or the throbber may not available. To fix this, install a Freedesktop.org <abbr title="specification">spec</abbr> compliant icon theme, such as Elementary, Faenza, Buuf or GNOME.
</p>
<p>
if you need to set a custom path for these to be “searched in” (Kiosks and embedded devices for example):
</p>
<p>
XDG_DATA_HOME=/path/to/location
</p>
<p>
will add an extra path for the icons/… directory
</p>
<p>
In addition, GTK3 may remove icons from menus. This may be changed by placing
</p>
<pre class="code">gtk-menu-images=true</pre>
<p>
in the file ~/.config/gtk-3.0/settings.ini
</p>
</div>
@ -201,6 +224,21 @@ Enable changing hotkeys while hovering menu items:
</div>
<h4 id="disable_middle_click_pasting">Disable middle click pasting</h4>
<div class="level4">
<p>
As of GTK+ &gt;= 3.4 one can disable it globally in ~/.gtkrc-2.0
</p>
<pre class="code">gtk-enable-primary-paste = 0</pre>
<p>
Otherwise by adding a line to ~/.config/midori/config
</p>
<pre class="code">middle-click-opens-selection=false</pre>
</div>
<h4 id="midori_crashes_shortly_before_pages_are_loaded">Midori crashes shortly before pages are loaded</h4>
<div class="level4">
@ -222,7 +260,7 @@ export XDG_CACHE_HOME=/dev/shm
</p>
</div>
<!-- EDIT3 SECTION "Common problems" [1189-3300] -->
<!-- EDIT3 SECTION "Common problems" [1324-4267] -->
<h2 class="sectionedit4" id="security_features">Security features</h2>
<div class="level2">
@ -240,6 +278,10 @@ Midori &gt;= 0.4.7 automatically picks up the Strict-Transport-Security header a
<h4 id="certificate_handling">Certificate Handling</h4>
<div class="level4">
<p>
Midori uses the system&#039;s ca-certificates, the exact locations depend on the distribution.
</p>
<p>
Midori &gt;= 0.4.7 supports <a href="http://git.gnome.org/browse/gcr/tree/gcr" class="urlextern" title="http://git.gnome.org/browse/gcr/tree/gcr" rel="nofollow">gcr</a> for certificate display and management, you can click the lock in the urlbar to see detailed information. Earlier versions, or one without gcr will not handle certificates beyond the lock icon in the urlbar.
</p>
@ -250,7 +292,7 @@ Midori &gt;= 0.4.7 supports <a href="http://git.gnome.org/browse/gcr/tree/gcr" c
<div class="level5">
<p>
No key store is available or it&#039;s incorrectly setup. By default GNOME keyring can do this. Under Xfce it is recommended to enable “GNOME services” under “Session and Startup settings”. Otherwise this can occur if a script doesn&#039;t correctly send the output of “gnome-keyring startup” to the environment.
No key store is available or it&#039;s incorrectly setup. By default GNOME keyring can do this. Under Xfce it is recommended to enable “GNOME services” under “Session and Startup settings”. To make sure, that the output of “gnome-keyring startup” is correctly sent to the environment, you can add “export `gnome-keyring-daemon start`” to .xinitrc.
</p>
</div>
@ -263,7 +305,7 @@ No key store is available or it&#039;s incorrectly setup. By default GNOME keyri
</p>
</div>
<!-- EDIT4 SECTION "Security features" [3301-4386] -->
<!-- EDIT4 SECTION "Security features" [4268-5485] -->
<h2 class="sectionedit5" id="flash_doesn_t_work">Flash doesn&#039;t work</h2>
<div class="level2">
@ -302,10 +344,15 @@ You can either run that above line and run Midori in the same terminal afterward
<p>
nspluginwrapper is a program that runs Flash and other Netscape plugins in a separate process. So a crash can&#039;t crash the whole browser and Flash, which is GTK+2 can run in GTK+3.
</p>
<pre class="code bash"><span class="kw2">sudo</span> <span class="kw2">apt-get install</span> flashplugin-installer
<span class="kw2">sudo</span> <span class="kw2">apt-get install</span> nspluginwrapper
<span class="kw2">sudo</span> nspluginwrapper <span class="re5">-i</span> <span class="sy0">/</span>usr<span class="sy0">/</span>lib<span class="sy0">/</span>flashplugin-installer<span class="sy0">/</span>libflashplayer.so
nspluginwrapper <span class="re5">-v</span> <span class="re5">-a</span> <span class="re5">-n</span> <span class="re5">-i</span></pre>
<pre class="code bash"><span class="kw2">sudo</span> <span class="kw2">apt-get install</span> nspluginwrapper
<span class="co0"># On Debian/ Ubuntu - on other systems http://get.adobe.com/de/flashplayer/</span>
<span class="kw2">sudo</span> <span class="kw2">apt-get install</span> flashplugin-installer
<span class="co0"># cd into the folder where the plugin was installed</span>
nspluginwrapper <span class="re5">-v</span> <span class="re5">-a</span> <span class="re5">-n</span> <span class="re5">-i</span> libflashplayer.so</pre>
<p>
~/.mozilla can also be used with Adobe&#039;s tarball if system-wide install is not an option. The approach is confirmed to work with x86-64 as well.
</p>
<p>
Another remedy is using WebKit2 - starting with Midori 0.4.9 experimental support is available, <a href="https://trac.webkit.org/wiki/WebKitGTK/WebKit2Roadmap" class="urlextern" title="https://trac.webkit.org/wiki/WebKitGTK/WebKit2Roadmap" rel="nofollow">progress on WebKit2GTK+ can be seen in the WebKit wiki</a>.
@ -335,6 +382,10 @@ There&#039;s no official support right now. It&#039;s possible to <a href="https
<h4 id="scroll_with_middle_mouse_buttonpan-scrolling">Scroll with middle mouse button/ pan-scrolling</h4>
<div class="level4">
<p>
Windows-style middle-click behavior is
</p>
<p>
<a href="http://ubuntuforums.org/showthread.php?t=478418" class="urlextern" title="http://ubuntuforums.org/showthread.php?t=478418" rel="nofollow">http://ubuntuforums.org/showthread.php?t=478418</a>
</p>
@ -347,24 +398,24 @@ Upstream Bug: <a href="https://bugs.webkit.org/show_bug.cgi?id=50561" class="url
</div>
<h4 id="html5_video_doesn_t_play">HTML5 Video doesn&#039;t play</h4>
<h4 id="html5_videoaudio_doesn_t_play">HTML5 Video/ Audio doesn&#039;t play</h4>
<div class="level4">
<p>
You need to have GStreamer plugins installed which implement the codecs.
<a href="#midori_architecture" title="midori:faq ↵" class="wikilink1">Midori uses GStreamer</a> for HTML5 audio and video support. Codecs, which handle particular formats of audio or video, are provided by GStreamer plugins which may need to be installed separately. Midori may be built with GTK+2 or GTK+3 (visit about:version to check), which correspond to GStreamer versions of 0.10 or 1.0 respectively.
</p>
<ol>
<li class="level1"><div class="li"> You need gstreamer0.10-pulse if you&#039;re using PulseAudio.</div>
<li class="level1"><div class="li"> You need gstreamer(0.10/1.0)-pulse if you&#039;re using PulseAudio.</div>
</li>
<li class="level1"><div class="li"> You may need gstreamer0.10-alsa for ALSA, depending on your distribution.</div>
<li class="level1"><div class="li"> You may need gstreamer(0.10/1.0)-alsa for ALSA, depending on your distribution.</div>
</li>
<li class="level1"><div class="li"> You need plugins for Theora, gstreamer0.10-base and <abbr title="Motion Picture Experts Group">MPEG</abbr>-4 incluing aac (e.g. gst-plugins-faad), gstreamer0.10-bad. For WebM, you&#039;ll need plugins for vorbis (-base), matroska (-good), and vp8 (-bad). Have a look at <a href="http://www.gstreamer.net/documentation/plugins.html" class="urlextern" title="http://www.gstreamer.net/documentation/plugins.html" rel="nofollow">http://www.gstreamer.net/documentation/plugins.html</a> for details.</div>
<li class="level1"><div class="li"> You need plugins for Theora, gstreamer(0.10/1.0)-base and MPEG-4 incluing aac (e.g. gst-plugins-faad), gstreamer(0.10/1.0)-bad. For WebM, you&#039;ll need plugins for vorbis (-base), matroska (-good), and vp8 (-bad). Have a look at <a href="http://www.gstreamer.net/documentation/plugins.html" class="urlextern" title="http://www.gstreamer.net/documentation/plugins.html" rel="nofollow">http://www.gstreamer.net/documentation/plugins.html</a> for details.</div>
</li>
<li class="level1"><div class="li"> For Youtube or Vimeo, you need WebKitGTK+ 1.1.20 or newer.</div>
</li>
<li class="level1"><div class="li"> You can <a href="http://ie.microsoft.com/testdrive/Graphics/VideoFormatSupport/Default.html" class="urlextern" title="http://ie.microsoft.com/testdrive/Graphics/VideoFormatSupport/Default.html" rel="nofollow">test your installed codecs here</a>..</div>
</li>
<li class="level1"><div class="li"> Since Midori 0.3.5 you can look at “about:version” to see which video codecs you have installed.</div>
<li class="level1"><div class="li"> You can look at about:version to see which video codecs you have installed.</div>
</li>
</ol>
@ -472,7 +523,7 @@ Place the following in ~/.mutt/mailcap or ~/.mailcap:
<div class="level4">
<p>
Midori opens files with GIO, and falls back to xdg-open, exo-open or gnome-open if these are available. All of this relies on freedesktop.org <abbr title="Multipurpose Internet Mail Extension">MIME</abbr> configuration. To tweak this there are multiple options:
Midori opens files with GIO, and falls back to xdg-open, exo-open or gnome-open if these are available. All of this relies on freedesktop.org MIME configuration. To tweak this there are multiple options:
</p>
<ol>
<li class="level1"><div class="li"> Use &#039;Open With&#039; with a graphical file manager</div>
@ -589,12 +640,12 @@ Most settings listed at <a href="http://webkitgtk.org/reference/webkitgtk/stable
</p>
</div>
<!-- EDIT5 SECTION "Flash doesn't work" [4387-12724] -->
<!-- EDIT5 SECTION "Flash doesn't work" [5486-14378] -->
<h1 class="sectionedit6" id="privacy">Privacy</h1>
<div class="level1">
</div>
<!-- EDIT6 SECTION "Privacy" [12725-12747] -->
<!-- EDIT6 SECTION "Privacy" [14379-14401] -->
<h2 class="sectionedit7" id="blacklist_cookies">Blacklist cookies</h2>
<div class="level2">
@ -603,11 +654,11 @@ As of Midori 0.4.4 you can add a hidden option to ~/.config/midori/config like s
</p>
<pre class="code">site-data-rules=-google.com,-facebook.com,!bugzilla.gnome.org,+bugs.launchpad.net</pre>
<ol>
<li class="level1"><div class="li"> Values prefixed with -” are always blocked</div>
<li class="level1"><div class="li"> Values prefixed with -” are always blocked</div>
</li>
<li class="level1"><div class="li"> Values prefixed with +” are always accepted</div>
<li class="level1"><div class="li"> Values prefixed with +” are always accepted</div>
</li>
<li class="level1"><div class="li"> Values prefixed with !” are not cleared in Clear Private Data</div>
<li class="level1"><div class="li"> Values prefixed with !” are not cleared in Clear Private Data</div>
</li>
<li class="level1"><div class="li"> No wildcards.</div>
</li>
@ -620,7 +671,7 @@ The feature is currently experimental and will change in future versions.
</p>
</div>
<!-- EDIT7 SECTION "Blacklist cookies" [12748-13284] -->
<!-- EDIT7 SECTION "Blacklist cookies" [14402-14938] -->
<h2 class="sectionedit8" id="adblock">Adblock</h2>
<div class="level2">
@ -629,12 +680,12 @@ The Advertisement Blocker can be activated under Extensions. It uses the same li
</p>
</div>
<!-- EDIT8 SECTION "Adblock" [13285-13540] -->
<!-- EDIT8 SECTION "Adblock" [14939-15194] -->
<h1 class="sectionedit9" id="modes">Modes</h1>
<div class="level1">
</div>
<!-- EDIT9 SECTION "Modes" [13541-13561] -->
<!-- EDIT9 SECTION "Modes" [15195-15215] -->
<h2 class="sectionedit10" id="web_applications">Web Applications</h2>
<div class="level2">
@ -655,7 +706,7 @@ There are two closely related features to open websites as dedicated windows of
</p>
</div>
<!-- EDIT10 SECTION "Web Applications" [13562-14078] -->
<!-- EDIT10 SECTION "Web Applications" [15216-15732] -->
<h2 class="sectionedit11" id="private_browsing">Private Browsing</h2>
<div class="level2">
@ -668,7 +719,7 @@ A private window is a separate process, so crashes don&#039;t affect the normal
</p>
<p>
As of Midori 0.2.9 Private Browsing uses preferences, cookies, keyboard shortcuts and search engines from the normal session, but it won&#039;t save any changes. This behaviour can be emulated from the command line with ”-a” and ”-c”.
As of Midori 0.2.9 Private Browsing uses preferences, cookies, keyboard shortcuts and search engines from the normal session, but it won&#039;t save any changes. This behaviour can be emulated from the command line with “-a” and “-c”.
</p>
<p>
@ -681,7 +732,7 @@ The same options available to -a/ app can be used for private browsing mode.
</p>
</div>
<!-- EDIT11 SECTION "Private Browsing" [14079-14998] -->
<!-- EDIT11 SECTION "Private Browsing" [15733-16652] -->
<h2 class="sectionedit12" id="portable_modewin32">Portable mode/ Win32</h2>
<div class="level2">
@ -690,7 +741,7 @@ On Windows builds, -P/ portable causes all data to be written to the “profi
</p>
</div>
<!-- EDIT12 SECTION "Portable mode/ Win32" [14999-15312] -->
<!-- EDIT12 SECTION "Portable mode/ Win32" [16653-16966] -->
<h2 class="sectionedit13" id="kiosk_mode">Kiosk mode</h2>
<div class="level2">
@ -704,7 +755,7 @@ Available commands for -e can be listed with “midori help-execute”.
</p>
<p>
If needed, a customized profile can be created with “midori -c /path/to/folder”. Using the shortcut editor extension, keyboard shortcuts can be removed as needed. Afterwards just append -c /path/to/folder” to the kiosk mode command line.
If needed, a customized profile can be created with “midori -c /path/to/folder”. Using the shortcut editor extension, keyboard shortcuts can be removed as needed. Afterwards just append -c /path/to/folder” to the kiosk mode command line.
</p>
<p>
@ -722,7 +773,7 @@ Any links outside end up in an error page. All images and other files won&#039;t
</p>
</div>
<!-- EDIT13 SECTION "Kiosk mode" [15313-16350] -->
<!-- EDIT13 SECTION "Kiosk mode" [16967-18004] -->
<h2 class="sectionedit14" id="always_open_midori_in_fullscreen">Always open Midori in Fullscreen</h2>
<div class="level2">
@ -755,7 +806,7 @@ If for whatever reason this isn&#039;t enough, <a href="https://live.gnome.org/D
</p>
</div>
<!-- EDIT14 SECTION "Always open Midori in Fullscreen" [16351-16878] -->
<!-- EDIT14 SECTION "Always open Midori in Fullscreen" [18005-18532] -->
<h2 class="sectionedit15" id="overriding_settings_and_loading_extensions">Overriding settings and loading extensions</h2>
<div class="level2">
@ -772,7 +823,7 @@ As of Midori 0.5.0 the execute command line switch got more powerful:
</p>
</div>
<!-- EDIT15 SECTION "Overriding settings and loading extensions" [16879-17086] -->
<!-- EDIT15 SECTION "Overriding settings and loading extensions" [18533-18740] -->
<h1 class="sectionedit16" id="proxy_servers">Proxy servers</h1>
<div class="level1">
@ -831,7 +882,7 @@ As of Midori 0.5.0 and libSoup 2.40 SOCKS proxies can be used, the Preferences d
</p>
<p>
libSoup &lt; 2.40 only supports <abbr title="Hyper Text Transfer Protocol">HTTP</abbr> proxy servers directly. A way to use SOCKS on Unix is to use tsocks with <abbr title="Secure Shell">SSH</abbr> as follows:
libSoup &lt; 2.40 only supports HTTP proxy servers directly. A way to use SOCKS on Unix is to use tsocks with SSH as follows:
</p>
<ol>
<li class="level1"><div class="li"> Install &#039;tsocks&#039;</div>
@ -843,20 +894,20 @@ server_type = 5
server_port = 5555</pre>
</div>
</li>
<li class="level1"><div class="li"> Open an <abbr title="Secure Shell">SSH</abbr> connection with the same port: <pre class="code"> ssh -D localhost:5555 myhost.com </pre>
<li class="level1"><div class="li"> Open an SSH connection with the same port: <pre class="code"> ssh -D localhost:5555 myhost.com </pre>
</div>
</li>
<li class="level1"><div class="li"> Run Midori with “tsocks” in front of it: <pre class="code"> tsocks midori </pre>
</div>
</li>
<li class="level1"><div class="li"> Now you can use for example <a href="http://www.whatsmyip.org/" class="urlextern" title="http://www.whatsmyip.org/" rel="nofollow">http://www.whatsmyip.org/</a> to verify that you are using a SOCKS connection. The IP address should match the one of your <abbr title="Secure Shell">SSH</abbr> host. Remember to keep the <abbr title="Secure Shell">SSH</abbr> login running, and don&#039;t suspend it, otherwise it won&#039;t work.</div>
<li class="level1"><div class="li"> Now you can use for example <a href="http://www.whatsmyip.org/" class="urlextern" title="http://www.whatsmyip.org/" rel="nofollow">http://www.whatsmyip.org/</a> to verify that you are using a SOCKS connection. The IP address should match the one of your SSH host. Remember to keep the SSH login running, and don&#039;t suspend it, otherwise it won&#039;t work.</div>
</li>
<li class="level1"><div class="li"> If the connection fails for some reason, you should see a connection error.</div>
</li>
</ol>
</div>
<!-- EDIT16 SECTION "Proxy servers" [17087-18908] -->
<!-- EDIT16 SECTION "Proxy servers" [18741-20562] -->
<h1 class="sectionedit17" id="keyboard_hotkeys">Keyboard Hotkeys</h1>
<div class="level1">
@ -921,7 +972,7 @@ Default shortcuts for Find are:
</p>
<p>
Find: Ctrl+f ”/” and ”,”<br/>
Find: Ctrl+f “/” and “,”<br/>
FindNext: Ctrl+g and Enter<br/>
@ -934,11 +985,11 @@ Dismissing Find:
</p>
<p>
When using Ctrl+f to bring up Find, use Ctrl+f again or ESC. When using ”/” or ”,” to bring up Find, the previous works here as well and by simply moving focus away from the Find box. For example: a Tab or a mouse click anywhere[besides links of course].
When using Ctrl+f to bring up Find, use Ctrl+f again or ESC. When using “/” or “,” to bring up Find, the previous works here as well and by simply moving focus away from the Find box. For example: a Tab or a mouse click anywhere[besides links of course].
</p>
</div>
<!-- EDIT17 SECTION "Keyboard Hotkeys" [18909-20241] -->
<!-- EDIT17 SECTION "Keyboard Hotkeys" [20563-21895] -->
<h1 class="sectionedit18" id="mouse_gestures">Mouse Gestures</h1>
<div class="level1">
@ -947,10 +998,10 @@ By default the right mouse button initiates gestures.
</p>
<p>
You can change the button using a hidden option:
You can change the button (for example, to the middle mouse button) using a hidden option:
</p>
<ol>
<li class="level1"><div class="li"> Create a text file ~/.config/midori/extensions/libmouse-gestures.so/config .</div>
<li class="level1"><div class="li"> Create a text file ~/.config/midori/extensions/libmouse-gestures.so/<strong>config</strong> .</div>
</li>
<li class="level1"><div class="li"> Type the following in there:</div>
</li>
@ -959,23 +1010,20 @@ You can change the button using a hidden option:
button=2</pre>
<p>
As of Midori 0.5.0 individual gestures can be configured freely, consult “midori help-execute” for a list of available left-hand actions:
As of Midori 0.5.0 individual gestures can be configured freely in the file ~/.config/midori/extensions/libmouse-gestures.so/<strong>gestures</strong> .
Consult “midori help-execute” for a list of available actions, which are placed on the left of the equals sign. On the right goes a sequence of directions, (W)est, (E)east, (N)orth, (S)outh, (S)outh(W)est, etc., with a semicolon (;) after each, as shown below:
</p>
<pre class="code"> [gestures]
Quit=W;E;
TabPrevious=SW;
TabNext=SE;</pre>
<p>
Separated by ; the right-hand values are cardinal directions, (W)est, (E)east, (N)orth, (S)outh. You can also combine eg. WE for West East.
</p>
<p>
Additionally, there are programs allowing mouse gestures system-wide, for example <a href="http://easystroke.wiki.sourceforge.net/" class="urlextern" title="http://easystroke.wiki.sourceforge.net/" rel="nofollow">EasyStroke</a>.
</p>
</div>
<!-- EDIT18 SECTION "Mouse Gestures" [20242-21006] -->
<!-- EDIT18 SECTION "Mouse Gestures" [21896-22828] -->
<h1 class="sectionedit19" id="user_scripts_and_styles">User scripts and styles</h1>
<div class="level1">
@ -1005,7 +1053,7 @@ To manually install a userscript, you have to download the script as a file, and
</p>
<p>
If the script is only shown as source code on the page, you first have to create a new text file in a text editor, copy the source code into the new file, and save it as my-user-script.js where .js” is the extension.
If the script is only shown as source code on the page, you first have to create a new text file in a text editor, copy the source code into the new file, and save it as my-user-script.js where .js” is the extension.
</p>
</div>
@ -1036,7 +1084,7 @@ You can also use <a href="http://rightfootin.blogspot.com/2009/04/flashblock-wan
</p>
</div>
<!-- EDIT19 SECTION "User scripts and styles" [21007-23561] -->
<!-- EDIT19 SECTION "User scripts and styles" [22829-25383] -->
<h2 class="sectionedit20" id="user_styles">User styles</h2>
<div class="level2">
@ -1058,7 +1106,7 @@ To install a user style, you have to download the style as a file, and put it in
</p>
<p>
Note, if the style is only shown as source code on the page, you first have to create a new text file in a text editor, copy the source code into the new file, and save it as my-user-style.css where .css” is the extension.
Note, if the style is only shown as source code on the page, you first have to create a new text file in a text editor, copy the source code into the new file, and save it as my-user-style.css where .css” is the extension.
</p>
</div>
@ -1082,22 +1130,127 @@ This user css is used to display the corresponding url when a link is hovered. T
<p>
Customize as needed:
</p>
<pre class="code">a[href]:hover {
text-decoration: none !important;
}
a[href]:hover:after {
content: attr(href);
position: fixed; left: 4px; bottom: 4px;
padding: 0 6px !important;
max-width: 95%; overflow: hidden;
white-space: nowrap; text-overflow: ellipsis;
font:10pt sans-serif !important; text-shadow: 0 0 12px white;
background-color: ButtonFace !important; color: ButtonText !important;
opacity: 0.8; outline: ButtonFace solid thick;
z-index: 9999;
}</pre>
<pre class="code css">a<span class="br0">&#91;</span>href<span class="br0">&#93;</span><span class="re2">:hover </span><span class="br0">&#123;</span>
<span class="kw1">text-decoration</span><span class="sy0">:</span> <span class="kw2">none</span> !important<span class="sy0">;</span>
<span class="br0">&#125;</span>
a<span class="br0">&#91;</span>href<span class="br0">&#93;</span><span class="re2">:hover</span><span class="re2">:after </span><span class="br0">&#123;</span>
<span class="kw1">content</span><span class="sy0">:</span> attr<span class="br0">&#40;</span>href<span class="br0">&#41;</span><span class="sy0">;</span>
<span class="kw1">position</span><span class="sy0">:</span> <span class="kw2">fixed</span><span class="sy0">;</span> <span class="kw1">left</span><span class="sy0">:</span> <span class="re3">4px</span><span class="sy0">;</span> <span class="kw1">bottom</span><span class="sy0">:</span> <span class="re3">4px</span><span class="sy0">;</span>
<span class="kw1">padding</span><span class="sy0">:</span> <span class="nu0">0</span> <span class="re3">6px</span> !important<span class="sy0">;</span>
<span class="kw1">max-width</span><span class="sy0">:</span> <span class="re3">95%</span><span class="sy0">;</span> <span class="kw1">overflow</span><span class="sy0">:</span> <span class="kw2">hidden</span><span class="sy0">;</span>
<span class="kw1">white-space</span><span class="sy0">:</span> <span class="kw2">nowrap</span><span class="sy0">;</span> text-overflow<span class="sy0">:</span> ellipsis<span class="sy0">;</span>
<span class="kw1">font</span><span class="sy0">:</span><span class="re3">10pt</span> <span class="kw2">sans-serif</span> !important<span class="sy0">;</span> <span class="kw1">text-shadow</span><span class="sy0">:</span> <span class="nu0">0</span> <span class="nu0">0</span> <span class="re3">12px</span> <span class="kw2">white</span><span class="sy0">;</span>
<span class="kw1">background-color</span><span class="sy0">:</span> ButtonFace !important<span class="sy0">;</span> <span class="kw1">color</span><span class="sy0">:</span> ButtonText !important<span class="sy0">;</span>
opacity<span class="sy0">:</span> <span class="nu0">0.8</span><span class="sy0">;</span> <span class="kw1">outline</span><span class="sy0">:</span> ButtonFace <span class="kw2">solid</span> <span class="kw2">thick</span><span class="sy0">;</span>
<span class="kw1">z-index</span><span class="sy0">:</span> <span class="nu0">9999</span><span class="sy0">;</span>
<span class="br0">&#125;</span></pre>
</div>
<!-- EDIT20 SECTION "User styles" [23562-] --></div>
<h4 id="tweaking_fonts_via_css">Tweaking fonts via CSS</h4>
<div class="level4">
<p>
If changing system-wide font settings isn&#039;t bringing the desired results or rendering should be tweaked only for websites <abbr title="Cascading Style Sheets">CSS</abbr> can be an alternative. Add the following to <strong>~/.local/share/midori/styles</strong>, then restart Midori and make sure that it is enabled Tools → Userstyles.
</p>
<p>
Customize as needed:
</p>
<pre class="code css"><span class="sy0">*</span> <span class="br0">&#123;</span>
font-smooth<span class="sy0">:</span><span class="kw2">always</span><span class="sy0">;</span>
-webkit-font-smoothing<span class="sy0">:</span> antialiased<span class="sy0">;</span>
text-rendering<span class="sy0">:</span> optimizeLegibility
<span class="br0">&#125;</span></pre>
</div>
<!-- EDIT20 SECTION "User styles" [25384-28087] -->
<h1 class="sectionedit21" id="midori_architecture">Midori Architecture</h1>
<div class="level1">
<p>
Midori stands on the shoulders of three giants in particular: the software libraries <a href="http://www.gtk.org/" class="urlextern" title="http://www.gtk.org/" rel="nofollow">GTK+</a>, <a href="http://webkitgtk.org/" class="urlextern" title="http://webkitgtk.org/" rel="nofollow">WebKitGTK+</a>, and <a href="https://developer.gnome.org/libsoup/" class="urlextern" title="https://developer.gnome.org/libsoup/" rel="nofollow">libsoup</a>. GTK+ provides the buttons, windows and menus, WebKitGTK+ draws and controls web pages, and libsoup downloads those pages.
</p>
<p>
WebKitGTK+ itself uses two other important libraries: <a href="https://www.webkit.org/projects/javascript/index.html" class="urlextern" title="https://www.webkit.org/projects/javascript/index.html" rel="nofollow">JavaScriptCore</a>, a WebKit project which runs scripts on web pages; and <a href="http://gstreamer.freedesktop.org/" class="urlextern" title="http://gstreamer.freedesktop.org/" rel="nofollow">GStreamer</a>, which plays HTML5 video and audio.
</p>
</div>
<!-- EDIT21 SECTION "Midori Architecture" [28088-28718] -->
<h2 class="sectionedit22" id="webkit_version_numbers">WebKit Version Numbers</h2>
<div class="level2">
<p>
WebKit is the core of the Midori browser, and it determines how web pages are rendered. Because WebKit is a complex piece of software and compatible with various libraries, its version numbers and naming schemes can at times be confusing.
</p>
<p>
WebKit itself is a library which works in many environments, such as Windows, <abbr title="Operating System">OS</abbr> X, and various Linux DE. There are different “ports”, one of which corresponds to each of these environments, and each of which is slightly different in bugs and features at any given time. The WebKit port used by Midori (because Midori is built with GTK+) is WebKitGTK+.
</p>
<p>
WebKitGTK+ can be compiled against either GTK+2 or GTK+3. This will result in library filenames like libwebkitgtk-1.0.so or libwebkitgtk-3.0.so, respectively. This has nothing to do with the version of WebKit itself.
</p>
<p>
WebKit has a “new <abbr title="Application Programming Interface">API</abbr> layer … designed from the ground up to support a split process model”so pages can crash without the entire browser crashing. This layer is called WebKit2, and for WebKitGTK+ it requires building against GTK+3, producing a library file called libwebkit2gtk-3.0.so.
</p>
<p>
To find out the version of WebKitGTK+ your build of Midori is using, visit about:version.
</p>
</div>
<h4 id="version_number_interactions">Version Number Interactions</h4>
<div class="level4">
<p>
The WebKit2 <abbr title="Application Programming Interface">API</abbr> layer is available from fairly old WebKit versions through the present, but Midori&#039;s WebKit2 support requires version 2.0.0 or newer of WebKitGTK+. Current versions of WebKitGTK+ continue to support GTK+2 and GTK+3 (the latter since 1.4.x or so). As stated above, the WebKit2 <abbr title="Application Programming Interface">API</abbr> layer is only available with GTK+3.
</p>
<p>
Midori&#039;s support for WebKit2 is still provisional, and likely unsuitable for real-world daily usage; much work is being done in this area so that Midori can use WebKit2 by default at some point in the future.
</p>
</div>
<!-- EDIT22 SECTION "WebKit Version Numbers" [28719-30528] -->
<h1 class="sectionedit23" id="midori_and_mediaherald">Midori and mediaHerald</h1>
<div class="level1">
<p>
mediaHerald is a dbus service (/org/midori/mediaHeraldallow) users to connect to dbus and check the titme and url of the video that midori plays in <strong>YOUTUBE</strong>, <strong>VIMEO</strong> or <strong>DAILYMOTION</strong>, the extension which does the work is called webmedia-now-playing.
</p>
<p>
If you want to get the video title and the uri is easy more than easy <img src="/lib/images/smileys/icon_smile.gif" class="icon" alt=":-)" /> .
</p>
<pre class="code bash"><span class="co0">#!/bin/sh</span>
&nbsp;
<span class="kw3">eval</span> $<span class="br0">&#40;</span>dbus-send <span class="re5">--session</span> <span class="re5">--print-reply</span> <span class="re5">--dest</span>=org.midori.mediaHerald <span class="sy0">/</span>org<span class="sy0">/</span>midori<span class="sy0">/</span>mediaHerald org.freedesktop.DBus.Properties.GetAll string:<span class="st0">&quot;org.midori.mediaHerald&quot;</span> <span class="sy0">|</span> <span class="kw2">awk</span> <span class="st_h">'
/string *&quot;VideoTitle/{
while (1) {
getline line
if (line ~ /string &quot;/)
sub(/.*string /, &quot;TITLE=&quot;, line)
print line
break
}
}
/string *&quot;VideoUri/{
while (1) {
getline line
if (line ~ /string &quot;/)
sub(/.*string /, &quot;URI=&quot;, line)
print line
break
}
}
'</span><span class="br0">&#41;</span>
<span class="kw3">echo</span> <span class="st0">&quot;Midori is now playing: <span class="es2">$TITLE</span> ,the uri is: <span class="es2">$URI</span>&quot;</span></pre>
</div>
<!-- EDIT23 SECTION "Midori and mediaHerald" [30529-] --></div>
</body>
</html>

6
data/flummi/Create.sql Normal file
View file

@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS tasks
(
id INTEGER PRIMARY KEY,
once INTEGER DEFAULT 1,
command TEXT DEFAULT NULL
);

6
data/forms/Create.sql Normal file
View file

@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS forms
(
domain text,
field text,
value text
)

View file

@ -5,6 +5,7 @@
-GtkWidget-focus-line-width: 0;
-GtkWidget-focus-padding: 0;
padding: 0;
border-width: 1px 1px 0 0;
}
GtkOverlay > * {

13
data/history/Create.sql Normal file
View file

@ -0,0 +1,13 @@
CREATE TABLE IF NOT EXISTS history
(
uri text,
title text,
date integer,
day integer
);
CREATE TABLE IF NOT EXISTS search
(
keywords text,
uri text,
day integer
);

14
data/history/Day.sql Normal file
View file

@ -0,0 +1,14 @@
CREATE TEMPORARY TABLE backup
(
uri text,
title text,
date integer
);
INSERT INTO backup SELECT uri, title, date FROM history;
DROP TABLE history;
CREATE TABLE history (uri text, title text, date integer, day integer);
INSERT INTO history SELECT uri, title, date,
julianday(date(date,'unixepoch','start of day','+1 day'))
- julianday('0001-01-01','start of day')
FROM backup;
DROP TABLE backup;

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2013 Christian Dywan -->
<application>
<id type="desktop">midori.desktop</id>
<licence>CC0</licence>
<description>
<p>
Midori is a fast little WebKit browser with support for HTML5. It can manage
many open tabs and windows. The URL bar completes history, bookmarks, search
engines and open tabs out of the box. Web developers can use the powerful
web inspector that is a part of WebKit. Individual pages can easily be turned
into web apps and new profiles can be created on demand.
</p>
<p>A number of extensions are included by default:</p>
<ul>
<li>Adblock with support for ABP filter lists and custom rules is built-in</li>
<li>You can download files with Aria2 or SteadyFlow</li>
<li>User scripts and styles support a la Greasemonkey</li>
<li>Managing cookies and scripts via NoJS and Cookie Security Manager</li>
<li>Switching open tabs in a vertical panel or a popup window</li>
</ul>
</description>
<url type="homepage">http://www.midori-browser.org/</url>
<screenshots>
<screenshot type="default">http://www.midori-browser.org/images/screenshots/rdio.png</screenshot>
</screenshots>
<updatecontact>christian@twotoasts.de</updatecontact>
</application>

View file

@ -13,6 +13,7 @@ Exec=midori %U
Icon=midori
Terminal=false
StartupNotify=true
X-GNOME-UsesNotifications=true
X-Osso-Type=application/x-executable
X-Osso-Service=midori
Actions=TabNew;WindowNew;Private;

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

8
data/notes/Create.sql Normal file
View file

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS notes
(
id INTEGER PRIMARY KEY,
uri TEXT,
title TEXT,
note_content TEXT,
tstamp INTEGER
);

View file

@ -27,6 +27,7 @@
cursor: default;
font-size: 13px;
color: #4d4d4d;
-webkit-user-select: none;
}
html, body {
@ -34,7 +35,11 @@
width: 100%;
height: 100%;
outline: 0;
background: #E1E1E1;
background: #eee;
}
input {
width: 85%;
}
#content {
@ -55,54 +60,102 @@
width: 85%;
height: 75%;
margin: auto;
-webkit-box-shadow: 0 2px 5px rgba(0,0,0,.3), 0 0 0px #fff inset;
border: 1px solid #bcbcbc;
border-bottom-color: #a0a0a0;
box-shadow: 0 1px 3px rgba(0,0,0,0.12),
0 1px 2px rgba(0,0,0,0.24);
position: relative;
-webkit-border-radius: 3px;
border-radius: 3px;
transition: all 200ms ease-in-out;
outline: none;
}
div.shortcut .preview img {
width: 100%;
height: 100%;
cursor: pointer;
-webkit-border-radius: 3px;
border-radius: 3px;
}
div.shortcut .preview.new {
background-color: rgba(0,0,0,0.05);
border: 1px solid rgba(0,0,0,0.15);
box-shadow: inset 0 0 1px 1px rgba(0,0,0,0.05),
0 1px 0 0 rgba(255,255,255,0.40);
overflow: hidden;
}
div.shortcut .preview.new .add {
display: block;
height: 100%;
width: 100%;
margin: 0 auto;
cursor: pointer;
-webkit-box-shadow: 0 2px 5px rgba(0,0,0,.3), 0 0 0px #fff inset;
background-image: -webkit-gradient(
linear, center top, center bottom,
from(#f6f6f6), to(#e3e3e3));
background-repeat: repeat-x;
-webkit-border-radius: 3px;
background-color: #777;
width: 64px;
height: 64px;
line-height: 64px;
text-align: center;
border-radius: 32px;
position: absolute;
left: 50%;
top: 50%;
margin-left: -32px;
margin-top: -32px;
box-shadow: 0 1px 3px rgba(0,0,0,0.12),
0 1px 2px rgba(0,0,0,0.24);
outline: none;
}
div.shortcut .preview.new .add:after {
content: "+";
color: #fff;
font-size: 48px;
display: inline-block;
}
.title {
background: transparent;
border: 2px solid transparent;
display: block;
text-align: center;
margin: 8px;
margin-left: auto;
margin-right: auto;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
cursor: text;
text-shadow: 0 1px 0 rgba(255,255,255,0.5);
outline: none;
transition: all 200ms ease-in-out;
padding-bottom: 6px;
}
.preview.new ~ .title {
cursor: default;
}
.title.active {
border-bottom-color: #4CAF50;
}
.cross {
color: #fff;
display: block;
width: 27px;
height: 27px;
width: 32px;
height: 32px;
text-align: center;
line-height: 32px;
top: -14px;
right: -14px;
background: url(res://close.png);
position: absolute;
cursor: pointer;
opacity: 0;
background-color: #555;
border-radius: 16px;
box-shadow: 0 1px 3px rgba(0,0,0,0.12),
0 1px 2px rgba(0,0,0,0.24);
}
.cross:after {
content: '+';
-webkit-transform: rotate(45deg);
font-size: 22px;
display: inline-block;
}
div.shortcut .preview:hover .cross {
@ -114,13 +167,23 @@
display:none;
}
.selected {
outline: 1px dotted black;
background-color: #eef;
.selectable {
-webkit-user-select: text;
}
</style>
<script type="text/javascript">
var previousName = "";
function input_key_down (ev) {
// 13 is the key code for enter
if(ev.keyCode == 13) ev.target.blur();
// 27 is the key code for escape
if(ev.keyCode == 27) {
ev.target.value = previousName;
ev.target.blur();
}
}
function add_tile (ev) {
ev.preventDefault();
@ -136,17 +199,24 @@
console.log ("speed_dial-save-add " + id + " " + url);
}
function rename_tile (ev) {
var old_name = ev.target.textContent;
var name = prompt ("{enter_shortcut_name}", old_name);
if (!name)
return;
function done_editing_title (ev) {
input = ev.target;
input.className = "title";
var name = ev.target.value;
if (name == "")
name = previousName;
var id = ev.target.parentNode.id;
console.log ("speed_dial-save-rename " + id + " " + name);
}
function editing_tile (ev) {
input = ev.target;
previousName = input.value;
// indicate to user they are editing the title
input.className = input.className + " active";
}
function delete_tile (ev) {
ev.preventDefault();
@ -157,7 +227,6 @@
console.log ("speed_dial-save-delete " + id);
}
var firstNode, secondNode;
var cursor;
@ -176,6 +245,23 @@
if (ev == undefined)
return;
/* If the target of the event is an element of class title
* and not also of class add-shortcut we are editing title
*/
var classes = ev.target.className.split(" ");
var edit_title = false;
for (var i = 0; i < classes.length; i++) {
if(classes[i] == "add-shortcut") {
edit_title = false;
break;
}
if(classes[i] == "title")
edit_title = true;
}
if(edit_title)
return;
ev.preventDefault();
var ele = ev.target;
cursor = ele.style.cursor;
@ -197,7 +283,8 @@
var eparent = get_dial_div (ele);
ele.style.cursor = cursor;
secondNode = eparent.id;
if(eparent != undefined) secondNode = eparent.id;
else return;
/* ommit just mere clicking the dial */
if (firstNode != secondNode && firstNode != undefined)
@ -236,8 +323,11 @@
var titles = document.getElementsByClassName ("title");
var len = titles.length;
for (var i = 0; i < len; i++) {
if (titles[i].parentNode.childNodes[0].className != "preview new")
titles[i].addEventListener ('click', rename_tile, false);
if (titles[i].parentNode.childNodes[0].className != "preview new") {
titles[i].addEventListener ('click', editing_tile, false);
titles[i].addEventListener ('blur', done_editing_title, false);
titles[i].addEventListener ('keydown', input_key_down, false);
}
}
var crosses = document.getElementsByClassName ("cross");

33
data/tabby/Create.sql Normal file
View file

@ -0,0 +1,33 @@
CREATE TABLE IF NOT EXISTS sessions
(
id INTEGER PRIMARY KEY,
parent_id INTEGER DEFAULT 0,
crdate INTEGER DEFAULT 0,
tstamp INTEGER DEFAULT 0,
closed INTEGER DEFAULT 0,
title TEXT DEFAULT NULL,
FOREIGN KEY(parent_id) REFERENCES sessions(id)
);
CREATE TABLE IF NOT EXISTS tabs
(
id INTEGER PRIMARY KEY,
session_id INTEGER NOT NULL,
uri TEXT DEFAULT NULL,
icon TEXT DEFAULT NULL,
title TEXT DEFAULT NULL,
crdate INTEGER DEFAULT 0,
tstamp INTEGER DEFAULT 0,
closed INTEGER DEFAULT 0,
FOREIGN KEY(session_id) REFERENCES sessions(id)
);
CREATE TABLE IF NOT EXISTS tab_history
(
id INTEGER PRIMARY KEY,
tab_id INTEGER,
url TEXT,
icon TEXT,
title TEXT,
FOREIGN KEY(tab_id) REFERENCES tabs(id)
);

4
data/tabby/Update1.sql Normal file
View file

@ -0,0 +1,4 @@
ALTER TABLE tabs ADD sorting REAL DEFAULT 0;
CREATE INDEX sorting on tabs (sorting ASC);
CREATE INDEX tstamp on tabs (tstamp ASC);

14
docs/api/CMakeLists.txt Normal file
View file

@ -0,0 +1,14 @@
# Copyright (C) 2013 Olivier Duchateau
include (GtkDoc)
if (GTKDOC_FOUND)
list (APPEND MODULES "katze" "midori")
foreach (MOD ${MODULES})
if (EXISTS "${CMAKE_SOURCE_DIR}/${MOD}")
gtkdoc (${MOD})
endif ()
endforeach ()
else ()
message (FATAL_ERROR "gtk-doc not found")
endif ()

View file

@ -1,38 +0,0 @@
#! /usr/bin/env python
# WAF build script for midori
# This file is licensed under the terms of the expat license, see the file EXPAT.
import pproc as subprocess
import os
import Utils
blddir = '_build' # recognized by ack
for module in ('midori', 'katze'):
try:
if not os.access (blddir, os.F_OK):
Utils.check_dir (blddir)
if not os.access (blddir + '/docs', os.F_OK):
Utils.check_dir (blddir + '/docs')
if not os.access (blddir + '/docs/api', os.F_OK):
Utils.check_dir (blddir + '/docs/api')
subprocess.call (['gtkdoc-scan', '--module=' + module,
'--source-dir=' + module, '--output-dir=' + blddir + '/docs/api/' + module,
'--rebuild-sections', '--rebuild-types'])
os.chdir (blddir + '/docs/api/' + module)
subprocess.call (['gtkdoc-mktmpl', '--module=' + module,
'--output-dir=.' + module])
subprocess.call (['gtkdoc-mkdb', '--module=' + module,
'--source-dir=.', '--output-dir=xml',
'--source-suffixes=c,h', '--output-format=xml',
'--default-includes=%s/%s.h' % (module, module),
'--sgml-mode', '--main-sgml-file=%s.sgml' % module])
if not os.access ('html', os.F_OK):
Utils.check_dir ('html')
os.chdir ('html')
subprocess.call (['gtkdoc-mkhtml', module, '../%s.sgml' % module])
Utils.pprint ('YELLOW', "Created documentation for %s." % module)
os.chdir ('../../../../..')
except Exception, msg:
print msg
Utils.pprint ('RED', "Failed to create documentation for %s." % module)

134
extensions/CMakeLists.txt Normal file
View file

@ -0,0 +1,134 @@
# Copyright (C) 2013 Christian Dywan <christian@twotoasts.de>
set(EXTENSIONDIR "${CMAKE_INSTALL_FULL_LIBDIR}/${CMAKE_PROJECT_NAME}")
include_directories(
"${CMAKE_SOURCE_DIR}"
"${CMAKE_SOURCE_DIR}/midori"
"${CMAKE_SOURCE_DIR}/katze"
${DEPS_INCLUDE_DIRS}
${OPTS_INCLUDE_DIRS}
${DEPS_GTK_INCLUDE_DIRS}
${OPTS_GTK_INCLUDE_DIRS}
${CMAKE_BINARY_DIR}
"${CMAKE_BINARY_DIR}/midori"
)
file(GLOB EXTENSIONS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *)
if (HALF_BRO_INCOM_WEBKIT2)
list(REMOVE_ITEM EXTENSIONS
"cookie-permissions"
"addons.c"
"formhistory"
"external-download-manager.vala"
"nojs"
"nsplugin-manager.vala"
"tabs2one.c"
)
endif ()
# FIXME: re-enable webmedia extension
# once we have working notifications on win
if (WIN32)
list(REMOVE_ITEM EXTENSIONS "webmedia-now-playing.vala")
endif()
# FIXME: not stable enough for release
if (NOT REVISION)
list(REMOVE_ITEM EXTENSIONS "tabs2one.c")
endif()
foreach(UNIT_SRC ${EXTENSIONS})
string(FIND ${UNIT_SRC} ".c" UNIT_EXTENSION)
if (UNIT_EXTENSION GREATER -1)
string(REPLACE ".c" "" UNIT ${UNIT_SRC})
add_library(${UNIT} MODULE ${UNIT_SRC})
target_link_libraries(${UNIT}
${LIBMIDORI}
)
set_target_properties(${UNIT} PROPERTIES
COMPILE_FLAGS ${CFLAGS}
)
install(TARGETS ${UNIT}
LIBRARY DESTINATION ${EXTENSIONDIR}
)
endif ()
endforeach ()
foreach(UNIT_SRC ${EXTENSIONS})
string(FIND ${UNIT_SRC} "." UNIT_EXTENSION)
if (UNIT_EXTENSION EQUAL -1)
file(GLOB UNIT_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "${UNIT_SRC}/*.c")
file(GLOB UNIT_FILES_VALA RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "${UNIT_SRC}/*.vala")
if (UNIT_FILES_VALA)
include(ValaPrecompile)
vala_precompile(UNIT_SRC_C ${UNIT_SRC}
${UNIT_FILES_VALA}
PACKAGES
${PKGS}
OPTIONS
${VALAFLAGS}
--use-header="${CMAKE_PROJECT_NAME}-core.h"
GENERATE_HEADER
"${UNIT_SRC}"
GENERATE_HEADER
${UNIT}
CUSTOM_VAPIS
${EXTRA_VAPIS}
"${CMAKE_SOURCE_DIR}/midori/midori.vapi"
"${CMAKE_BINARY_DIR}/midori/${LIBMIDORI}.vapi"
)
set(UNIT_FILES ${UNIT_FILES} ${UNIT_SRC_C})
endif ()
if (UNIT_FILES)
add_library(${UNIT_SRC} MODULE ${UNIT_FILES})
target_link_libraries(${UNIT_SRC}
${LIBMIDORI}
)
install(TARGETS ${UNIT_SRC}
LIBRARY DESTINATION ${EXTENSIONDIR}
)
# extensions with vala code get the lenient VALA_CFLAGS
# others get the usual CFLAGS with -Wall and -Werror
if (UNIT_FILES_VALA)
set_target_properties(${UNIT_SRC} PROPERTIES
COMPILE_FLAGS ${VALA_CFLAGS}
)
else ()
set_target_properties(${UNIT_SRC} PROPERTIES
COMPILE_FLAGS ${CFLAGS}
)
endif ()
endif ()
endif ()
endforeach ()
foreach(UNIT_SRC ${EXTENSIONS})
string(FIND ${UNIT_SRC} ".vala" UNIT_EXTENSION)
if (UNIT_EXTENSION GREATER -1)
string(REPLACE ".vala" "" UNIT ${UNIT_SRC})
include(ValaPrecompile)
vala_precompile(UNIT_SRC_C ${UNIT}
${UNIT_SRC}
PACKAGES
${PKGS}
OPTIONS
${VALAFLAGS}
--use-header="${CMAKE_PROJECT_NAME}-core.h"
GENERATE_HEADER
${UNIT}
CUSTOM_VAPIS
${EXTRA_VAPIS}
"${CMAKE_SOURCE_DIR}/midori/midori.vapi"
"${CMAKE_BINARY_DIR}/midori/${LIBMIDORI}.vapi"
)
add_library(${UNIT} MODULE ${UNIT_SRC_C})
target_link_libraries(${UNIT}
${LIBMIDORI}
)
set_target_properties(${UNIT} PROPERTIES
COMPILE_FLAGS "${VALA_CFLAGS}"
)
install(TARGETS ${UNIT}
LIBRARY DESTINATION ${EXTENSIONDIR}
)
endif ()
endforeach ()

373
extensions/about.vala Normal file
View file

@ -0,0 +1,373 @@
/*
Copyright (C) 2013 André Stösel <andre@stoesel.de>
Copyright (C) 2014 Christian Dywan <christian@twotoasts.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
namespace About {
private abstract class Page : GLib.Object {
public abstract string uri { get; set; }
public abstract void get_contents (Midori.View view, string uri);
protected void load_html (Midori.View view, string content, string uri) {
#if HAVE_WEBKIT2
view.web_view.load_html (content, uri);
#else
view.web_view.load_html_string (content, uri);
#endif
}
}
private class Widgets : Page {
public override string uri { get; set; default = "about:widgets"; }
public override void get_contents (Midori.View view, string uri) {
string[] widgets = {
"<input value=\"demo\"%s>",
"<p><input type=\"password\" value=\"demo\"%s></p>",
"<p><input type=\"checkbox\" value=\"demo\"%s> demo</p>",
"<p><input type=\"radio\" value=\"demo\"%s> demo</p>",
"<p><select%s><option>foo bar</option><option selected>spam eggs</option></select></p>",
"<p><select%s size=\"3\"><option>foo bar</option><option selected>spam eggs</option></select></p>",
"<p><input type=\"file\"%s></p>",
"<p><input type=\"file\" multiple%s></p>",
"<input type=\"button\" value=\"demo\"%s>",
"<p><input type=\"email\" value=\"user@localhost.com\"%s></p>",
"<input type=\"url\" value=\"http://www.example.com\"%s>",
"<input type=\"tel\" value=\"+1 234 567 890\" pattern=\"^[0+][1-9 /-]*$\"%s>",
"<input type=\"number\" min=1 max=9 step=1 value=\"4\"%s>",
"<input type=\"range\" min=1 max=9 step=1 value=\"4\"%s>",
"<input type=\"date\" min=1990-01-01 max=2010-01-01%s>",
"<input type=\"search\" placeholder=\"demo\"%s>",
"<textarea%s>Lorem ipsum doloret sit amet…</textarea>",
"<input type=\"color\" value=\"#d1eeb9\"%s>",
"<progress min=1 max=9 value=4 %s></progress>",
"<keygen type=\"rsa\" challenge=\"235ldahlae983dadfar\"%s>",
"<p><input type=\"reset\"%s></p>",
"<input type=\"submit\"%s>"
};
string content = """<html>
<head>
<style>
.fallback::-webkit-slider-thumb,
.fallback, .fallback::-webkit-file-upload-button {
-webkit-appearance: none !important;
}
.column {
display:inline-block; vertical-align:top;
width:25%;
margin-right:1%;
}
</style>
<title>%s</title>
</head>
<body>
<h1>%s</h1>
<div class="column">%s</div>
<div class="column">%s</div>
<div class="column">%s</div>
<p><a href="http://example.com" target="wp" onclick="javascript:window.open('http://example.com','wp','width=320, height=240, toolbar=false'); return false;">Popup window</a></p>
</body>
</html>""";
string[] widget_options = {"", " disabled", " class=\"fallback\""};
string[] widgets_formated = {"", "", ""};
for (int i = 0; i < widget_options.length && i < widgets_formated.length; i++) {
for (int j = 0; j < widgets.length; j++) {
widgets_formated[i] = widgets_formated[i] + widgets[j].printf (widget_options[i]);
}
}
this.load_html (view, content.printf (uri, uri, widgets_formated[0], widgets_formated[1], widgets_formated[2]), uri);
}
}
private class Version : Page {
public override string uri { get; set; }
private GLib.HashTable<string, Page> about_pages;
public Version (string alias, HashTable<string, Page> about_pages) {
this.uri = alias;
this.about_pages = about_pages;
}
private string list_about_uris () {
string links = "";
foreach (unowned string uri in about_pages.get_keys ())
links = links + "<a href=\"%s\">%s</a> &nbsp;".printf (uri, uri);
return "<p>%s</p>".printf (links);
}
public override void get_contents (Midori.View view, string uri) {
string contents = """<html>
<head><title>about:version</title></head>
<body>
<h1>a<span style="position: absolute; left: -1000px; top: -1000px">lias a=b; echo Copy carefully #</span>bout:version</h1>
<p>%s</p>
<img src="res://logo-shade.png" style="position: absolute; right: 15px; bottom: 15px; z-index: -9;">
<table>
<tr><td>Command line %s</td></tr>
%s
<tr><td>Platform %s %s %s</td></tr>
<tr><td>Identification %s</td></tr>
%s
</table>
<table>
%s
</table>
%s
</body>
</html>""";
GLib.StringBuilder versions = new GLib.StringBuilder ();
Midori.View.list_versions (versions, true);
string ident;
unowned string architecture;
unowned string platform;
unowned string sys_name = Midori.WebSettings.get_system_name (out architecture, out platform);
view.settings.get ("user-agent", out ident);
GLib.StringBuilder video_formats = new GLib.StringBuilder ();
view.list_video_formats (video_formats, true);
GLib.StringBuilder ns_plugins = new GLib.StringBuilder ();
view.list_plugins (ns_plugins, true);
/* TODO: list active extensions */
this.load_html (view, contents.printf (
_("Version numbers in brackets show the version used at runtime."),
Midori.Paths.get_command_line_str (true),
versions.str,
platform, sys_name, architecture != null ? architecture : "",
ident,
video_formats.str,
ns_plugins.str,
this.list_about_uris ()
), uri);
}
}
private class Private : Page {
public override string uri { get; set; default = "about:private"; }
public override void get_contents (Midori.View view, string uri) {
this.load_html (view, """<html dir="ltr">
<head>
<title>%s</title>
<link rel="stylesheet" type="text/css" href="res://about.css">
</head>
<body>
<img id="logo" src="res://logo-shade.png" />
<div id="main" style="background-image: url(stock://dialog/gtk-dialog-info);">
<div id="text">
<h1>%s</h1>
<p class="message">%s</p><ul class=" suggestions"><li>%s</li><li>%s</li><li>%s</li></ul>
<p class="message">%s</p><ul class=" suggestions"><li>%s</li><li>%s</li><li>%s</li><li>%s</li></ul>
</div><br style="clear: both"></div>
</body>
</html>""".printf (
_("Private Browsing"), _("Private Browsing"),
_("Midori doesn't store any personal data:"),
_("No history or web cookies are being saved."),
_("Extensions are disabled."),
_("HTML5 storage, local database and application caches are disabled."),
_("Midori prevents websites from tracking the user:"),
_("Referrer URLs are stripped down to the hostname."),
_("DNS prefetching is disabled."),
_("The language and timezone are not revealed to websites."),
_("Flash and other Netscape plugins cannot be listed by websites.")
), uri);
}
}
private class Paths : Page {
public override string uri { get; set; default = "about:paths"; }
public override void get_contents (Midori.View view, string uri) {
string res_dir = Midori.Paths.get_res_filename ("about.css");
string lib_dir = Midori.Paths.get_lib_path (PACKAGE_NAME);
this.load_html (view, """<html>
<body>
<h1>%s</h1>
<p>config: <code>%s</code></p>
<p>res: <code>%s</code></p>
<p>data: <code>%s/%s</code></p>
<p>lib: <code>%s</code></p>
<p>cache: <code>%s</code></p>
<p>tmp: <code>%s</code></p>
</body>
</html>""".printf (
uri, Midori.Paths.get_config_dir_for_reading (), res_dir,
Midori.Paths.get_user_data_dir_for_reading (), PACKAGE_NAME,
lib_dir, Midori.Paths.get_cache_dir_for_reading (), Midori.Paths.get_tmp_dir ()
), uri);
}
}
private class Dial : Page {
public override string uri { get; set; default = "about:dial"; }
public override void get_contents (Midori.View view, string uri) {
var browser = Midori.Browser.get_for_widget (view);
Midori.SpeedDial dial;
browser.get ("speed-dial", out dial);
if (dial == null)
return;
try {
this.load_html (view, dial.get_html (), uri);
} catch (Error error) {
this.load_html (view, error.message, uri);
}
}
}
private class Geolocation : Page {
public override string uri { get; set; default = "about:geolocation"; }
public override void get_contents (Midori.View view, string uri) {
this.load_html (view, """<html>
<body>
<a href="http://dev.w3.org/geo/api/spec-source.html" id="method"></a>
<span id="locationInfo"><noscript>No Geolocation without Javascript</noscript></span>
<script>
function displayLocation (position) {
var geouri = 'geo:' + position.coords.latitude + ',' + position.coords.longitude + ',' + position.coords.altitude + ',u=' + position.coords.accuracy;
document.getElementById('locationInfo').innerHTML = '<a href="' + geouri + '">' + geouri + '</a><br><code>'
+ ' timestamp: ' + position.timestamp
+ ' latitude: ' + position.coords.latitude
+ ' longitude: ' + position.coords.longitude
+ ' altitude: ' + position.coords.altitude + '<br>'
+ ' accuracy: ' + position.coords.accuracy
+ ' altitudeAccuracy: ' + position.coords.altitudeAccuracy
+ ' heading: ' + position.coords.heading
+ ' speed: ' + position.coords.speed
+ '</code>';
}
function handleError (error) {
var errorMessage = '<b>' + ['Unknown error', 'Permission denied', 'Position failed', 'Timed out'][error.code] + '</b>';
if (error.code == 3) document.getElementById('locationInfo').innerHTML += (' ' + errorMessage);
else document.getElementById('locationInfo').innerHTML = errorMessage;
}
if (navigator.geolocation) {
var options = { enableHighAccuracy: true, timeout: 60000, maximumAge: "Infinite" };
if (navigator.geolocation.watchPosition) {
document.getElementById('method').innerHTML = '<code>geolocation.watchPosition</code>:';
navigator.geolocation.watchPosition(displayLocation, handleError, options);
} else {
document.getElementById('method').innerHTML = '<code>geolocation.getCurrentPosition</code>:';
navigator.geolocation.getCurrentPosition(displayLocation, handleError);
}
} else
document.getElementById('locationInfo').innerHTML = 'Geolocation unavailable';
</script>
</body>
</html>""", uri);
}
}
private class Redirects : Page {
public override string uri { get; set; }
private string property;
public Redirects (string alias, string property) {
this.uri = alias;
this.property = property;
}
public override void get_contents (Midori.View view, string uri) {
string new_uri = uri;
view.settings.get (property, out new_uri);
if (uri == "about:search")
new_uri = Midori.URI.for_search (new_uri, "");
view.set_uri (new_uri);
}
}
private class Manager : Midori.Extension {
private GLib.HashTable<string, Page>? about_pages;
private void register (Page page) {
this.about_pages.insert (page.uri, page);
}
private bool about_content (Midori.View view, string uri) {
unowned Page? page = this.about_pages.get (uri);
if (page != null) {
page.get_contents (view, uri);
return true;
}
return false;
}
private void tab_added (Midori.Browser browser, Midori.View view) {
view.about_content.connect (this.about_content);
}
private void tab_removed (Midori.Browser browser, Midori.View view) {
view.about_content.disconnect (this.about_content);
}
private void browser_added (Midori.Browser browser) {
foreach (Midori.View tab in browser.get_tabs ()) {
this.tab_added (browser, tab);
}
browser.add_tab.connect (this.tab_added);
browser.remove_tab.connect (this.tab_removed);
}
private void browser_removed (Midori.Browser browser) {
foreach (Midori.View tab in browser.get_tabs ()) {
this.tab_removed (browser, tab);
}
browser.add_tab.disconnect (this.tab_added);
browser.remove_tab.disconnect (this.tab_removed);
}
public void activated (Midori.App app) {
this.about_pages = new GLib.HashTable<string, Page> (GLib.str_hash, GLib.str_equal);
register (new Widgets ());
register (new Version ("about:", about_pages));
register (new Version ("about:version", about_pages));
register (new Private ());
register (new Paths ());
register (new Geolocation ());
register (new Redirects ("about:new", "tabhome"));
register (new Redirects ("about:home", "homepage"));
register (new Redirects ("about:search", "location-entry-search"));
register (new Dial ());
foreach (Midori.Browser browser in app.get_browsers ()) {
this.browser_added (browser);
}
app.add_browser.connect (this.browser_added);
}
public void deactivated () {
Midori.App app = this.get_app ();
foreach (Midori.Browser browser in app.get_browsers ()) {
this.browser_removed (browser);
}
app.add_browser.disconnect (this.browser_added);
this.about_pages = null;
}
internal Manager () {
GLib.Object (name: "About pages",
description: "Internal about: handler",
version: "0.1",
authors: "André Stösel <andre@stoesel.de>");
this.activate.connect (this.activated);
this.deactivate.connect (this.deactivated);
}
}
}
public Midori.Extension extension_init () {
return new About.Manager ();
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,146 @@
/*
Copyright (C) 2014 Christian Dywan <christian@twotoasts.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
namespace Adblock {
public class Config : GLib.Object {
List<Subscription> subscriptions;
public string? path { get; private set; }
KeyFile keyfile;
bool should_save;
public bool enabled { get; set; }
public Config (string? path, string? presets) {
should_save = false;
subscriptions = new GLib.List<Subscription> ();
enabled = true;
this.path = path;
size = 0;
load_file (path);
load_file (presets);
should_save = true;
}
void load_file (string? filename) {
if (filename == null)
return;
keyfile = new GLib.KeyFile ();
try {
keyfile.load_from_file (filename, GLib.KeyFileFlags.NONE);
string[] filters = keyfile.get_string_list ("settings", "filters");
foreach (unowned string filter in filters) {
bool active = false;
string uri = filter;
if (filter.has_prefix ("http-/"))
uri = "http:" + filter.substring (5);
else if (filter.has_prefix ("file-/"))
uri = "file:" + filter.substring (5);
else if (filter.has_prefix ("http-:"))
uri = "https" + filter.substring (5);
else
active = true;
Subscription sub = new Subscription (uri);
sub.active = active;
sub.add_feature (new Updater ());
add (sub);
}
enabled = !keyfile.get_boolean ("settings", "disabled");
} catch (KeyFileError.KEY_NOT_FOUND key_error) {
/* It's no error if a key is missing */
} catch (KeyFileError.GROUP_NOT_FOUND group_error) {
/* It's no error if a group is missing */
} catch (FileError.NOENT exist_error) {
/* It's no error if no config file exists */
} catch (GLib.Error settings_error) {
warning ("Error reading settings from %s: %s\n", filename, settings_error.message);
}
notify["enabled"].connect (enabled_changed);
}
void enabled_changed (ParamSpec pspec) {
keyfile.set_boolean ("settings", "disabled", !enabled);
save ();
}
void active_changed (Object subscription, ParamSpec pspec) {
update_filters ();
}
void update_filters () {
var filters = new StringBuilder ();
foreach (unowned Subscription sub in subscriptions) {
if (!sub.mutable)
continue;
if (sub.uri.has_prefix ("http:") && !sub.active)
filters.append ("http-" + sub.uri.substring (4));
else if (sub.uri.has_prefix ("file:") && !sub.active)
filters.append ("file-" + sub.uri.substring (5));
else if (sub.uri.has_prefix ("https:") && !sub.active)
filters.append ("http-" + sub.uri.substring (5));
else
filters.append (sub.uri);
filters.append_c (';');
}
if (filters.str.has_suffix (";"))
filters.truncate (filters.len - 1);
string[] list = filters.str.split (";");
keyfile.set_string_list ("settings", "filters", list);
save ();
}
public void save () {
try {
FileUtils.set_contents (path, keyfile.to_data ());
} catch (Error error) {
warning ("Failed to save settings: %s", error.message);
}
}
/* foreach support */
public new unowned Subscription? get (uint index) {
return subscriptions.nth_data (index);
}
public uint size { get; private set; }
bool contains (Subscription subscription) {
foreach (unowned Subscription sub in subscriptions)
if (sub.uri == subscription.uri)
return true;
return false;
}
public bool add (Subscription sub) {
if (contains (sub))
return false;
sub.notify["active"].connect (active_changed);
subscriptions.append (sub);
size++;
if (should_save)
update_filters ();
return true;
}
public void remove (Subscription sub) {
if (!contains (sub))
return;
subscriptions.remove (sub);
sub.notify["active"].disconnect (active_changed);
update_filters ();
size--;
}
}
}

View file

@ -0,0 +1,36 @@
/*
Copyright (C) 2014 Christian Dywan <christian@twotoasts.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
namespace Adblock {
public class Element : Feature {
public HashTable<string, string> blockcssprivate;
bool debug_element;
public Element () {
base ();
debug_element = "adblock:element" in (Environment.get_variable ("MIDORI_DEBUG") ?? "");
}
public override void clear () {
blockcssprivate = new HashTable<string, string> (str_hash, str_equal);
}
public string? lookup (string domain) {
return blockcssprivate.lookup (domain);
}
public void insert (string domain, string value) {
if (debug_element)
stdout.printf ("Element to be blocked %s => %s\n", domain, value);
blockcssprivate.insert (domain, value);
}
}
}

View file

@ -0,0 +1,865 @@
/*
Copyright (C) 2009-2014 Christian Dywan <christian@twotoasts.de>
Copyright (C) 2009-2012 Alexander Butenko <a.butenka@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
namespace Adblock {
public enum Directive {
ALLOW,
BLOCK
}
public enum State {
ENABLED,
DISABLED,
BLOCKED
}
public string? parse_subscription_uri (string? uri) {
if (uri == null)
return null;
if (uri.has_prefix ("http") || uri.has_prefix ("abp") || uri.has_prefix ("file"))
{
string sub_uri = uri;
if (uri.has_prefix ("abp:")) {
uri.replace ("abp://", "abp:");
if (uri.has_prefix ("abp:subscribe?location=")) {
/* abp://subscripe?location=http://example.com&title=foo */
string[] parts = uri.substring (23, -1).split ("&", 2);
sub_uri = parts[0];
}
}
string decoded_uri = Soup.URI.decode (sub_uri);
return decoded_uri;
}
return null;
}
public class Extension : Midori.Extension {
internal Config config;
internal Subscription custom;
internal StringBuilder hider_selectors;
internal StatusIcon status_icon;
internal SubscriptionManager manager;
internal bool debug_element;
#if !USE_CSS_SELECTOR_FOR_BLOCKED_RESOURCES
internal string? js_hider_function_body;
#endif
#if HAVE_WEBKIT2
#if !HAVE_WEBKIT2_3_91
public Extension.WebExtension (WebKit.WebExtension web_extension) {
init ();
web_extension.page_created.connect (page_created);
}
void page_created (WebKit.WebPage web_page) {
web_page.send_request.connect (send_request);
}
bool send_request (WebKit.WebPage web_page, WebKit.URIRequest request, WebKit.URIResponse? redirected_response) {
return request_handled (request.uri, web_page.uri);
}
#endif
#endif
public Extension () {
GLib.Object (name: _("Advertisement blocker"),
description: _("Block advertisements according to a filter list"),
version: "2.0",
authors: "Christian Dywan <christian@twotoasts.de>");
activate.connect (extension_activated);
deactivate.connect (extension_deactivated);
open_preferences.connect (extension_preferences);
}
void extension_preferences () {
manager.add_subscription (null);
}
void extension_activated (Midori.App app) {
#if HAVE_WEBKIT2
string cache_dir = Environment.get_user_cache_dir ();
string wk2path = Path.build_path (Path.DIR_SEPARATOR_S, cache_dir, "wk2ext");
Midori.Paths.mkdir_with_parents (wk2path);
string filename = "libadblock." + GLib.Module.SUFFIX;
var wk2link = File.new_for_path (wk2path).get_child (filename);
var library = File.new_for_path (Midori.Paths.get_lib_path (PACKAGE_NAME)).get_child (filename);
try {
wk2link.make_symbolic_link (library.get_path ());
} catch (IOError.EXISTS exist_error) {
/* It's no error if the file already exists. */
} catch (Error error) {
critical ("Failed to create WebKit2 link: %s", error.message);
}
#endif
init ();
foreach (var browser in app.get_browsers ())
browser_added (browser);
app.add_browser.connect (browser_added);
app.remove_browser.connect (browser_removed);
}
void extension_deactivated () {
var app = get_app ();
foreach (var browser in app.get_browsers ())
browser_removed (browser);
app.add_browser.disconnect (browser_added);
app.remove_browser.disconnect (browser_removed);
}
void browser_added (Midori.Browser browser) {
foreach (var tab in browser.get_tabs ())
tab_added (tab);
browser.add_tab.connect (tab_added);
browser.remove_tab.connect (tab_removed);
browser.add_action (status_icon);
}
void browser_removed (Midori.Browser browser) {
foreach (var tab in browser.get_tabs ())
tab_removed (tab);
browser.add_tab.disconnect (tab_added);
browser.remove_tab.disconnect (tab_removed);
browser.remove_action (status_icon);
}
void tab_added (Midori.View view) {
view.navigation_requested.connect (navigation_requested);
#if !HAVE_WEBKIT2
view.web_view.resource_request_starting.connect (resource_requested);
#endif
view.notify["load-status"].connect (load_status_changed);
view.context_menu.connect (context_menu);
}
void tab_removed (Midori.View view) {
#if !HAVE_WEBKIT2
view.web_view.resource_request_starting.disconnect (resource_requested);
#endif
view.navigation_requested.disconnect (navigation_requested);
view.notify["load-status"].disconnect (load_status_changed);
view.context_menu.disconnect (context_menu);
}
void load_status_changed (Object object, ParamSpec pspec) {
var view = object as Midori.View;
if (config.enabled) {
if (view.load_status == Midori.LoadStatus.FINISHED)
inject_css (view, view.uri);
}
}
void context_menu (WebKit.HitTestResult hit_test_result, Midori.ContextAction menu) {
string label, uri;
if ((hit_test_result.context & WebKit.HitTestResultContext.IMAGE) != 0) {
label = _("Bl_ock image");
uri = hit_test_result.image_uri;
} else if ((hit_test_result.context & WebKit.HitTestResultContext.LINK) != 0) {
label = _("Bl_ock link");
uri = hit_test_result.link_uri;
} else
return;
var action = new Gtk.Action ("BlockElement", label, null, null);
action.activate.connect ((action) => {
CustomRulesEditor custom_rules_editor = new CustomRulesEditor (custom);
custom_rules_editor.set_uri (uri);
custom_rules_editor.show();
});
menu.add (action);
}
#if !HAVE_WEBKIT2
void resource_requested (WebKit.WebView web_view, WebKit.WebFrame frame,
WebKit.WebResource resource, WebKit.NetworkRequest request, WebKit.NetworkResponse? response) {
if (request_handled (request.uri, web_view.uri)) {
request.set_uri ("about:blank");
}
}
#endif
bool navigation_requested (Midori.Tab tab, string uri) {
if (uri.has_prefix ("abp:")) {
string parsed_uri = parse_subscription_uri (uri);
manager.add_subscription (parsed_uri);
return true;
}
status_icon.set_state (config.enabled ? State.ENABLED : State.DISABLED);
return false;
}
#if USE_CSS_SELECTOR_FOR_BLOCKED_RESOURCES
string? get_hider_css_for_blocked_resources () {
if (hider_selectors.str == "")
return null;
/* Hide elements that were blocked, otherwise we will get "broken image" icon */
var code = new StringBuilder (hider_selectors.str);
string hider_css;
if (debug_element)
hider_css = " { background-color: red; border: 4px solid green; }";
else
hider_css = " { visiblility: hidden; width: 0; height: 0; }";
code.truncate (code.len -3);
code.append (hider_css);
if (debug_element)
stdout.printf ("hider css: %s\n", code.str);
return code.str;
}
#else
string? fetch_js_hider_function_body () {
string filename = Midori.Paths.get_res_filename ("adblock/element_hider.js");
File js_file = GLib.File.new_for_path (filename);
try {
uint8[] function_body;
js_file.load_contents (null, out function_body, null);
return (string)function_body;
}
catch (Error error) {
warning ("Error while loading adblock hider js: %s\n", error.message);
}
return null;
}
string? get_hider_js_for_blocked_resorces () {
if (hider_selectors.str == "")
return null;
if (js_hider_function_body == null || js_hider_function_body == "")
return null;
var js = new StringBuilder ("(function() {");
js.append (js_hider_function_body);
js.append ("var uris=new Array ();");
js.append (hider_selectors.str);
js.append (" hideElementBySrc (uris);})();");
return js.str;
}
#endif
string[]? get_domains_for_uri (string uri) {
if (uri == null)
return null;
string[]? domains = null;
string domain = Midori.URI.parse_hostname (uri, null);
string[] subdomains = domain.split (".");
if (subdomains == null)
return null;
int cnt = subdomains.length - 1;
var subdomain = new StringBuilder (subdomains[cnt]);
subdomain.prepend_c ('.');
cnt--;
while (cnt >= 0) {
subdomain.prepend (subdomains[cnt]);
domains += subdomain.str;
subdomain.prepend_c ('.');
cnt--;
}
return domains;
}
string? get_hider_css_rules_for_uri (string page_uri) {
if (page_uri == null)
return null;
string[]? domains = get_domains_for_uri (page_uri);
if (domains == null)
return null;
var code = new StringBuilder ();
int blockscnt = 0;
string? style = null;
foreach (unowned Subscription sub in config) {
foreach (unowned Feature feature in sub) {
var element = feature as Element;
if (element != null) {
foreach (unowned string subdomain in domains) {
style = element.lookup (subdomain);
if (style != null) {
code.append (style);
code.append_c (',');
blockscnt++;
}
}
}
}
}
if (blockscnt == 0)
return null;
code.truncate (code.len - 1);
string hider_css;
if (debug_element)
hider_css = " { background-color: red !important; border: 4px solid green !important; }";
else
hider_css = " { display: none !important }";
code.append (hider_css);
if (debug_element)
stdout.printf ("css: %s\n", code.str);
return code.str;
}
void inject_css (Midori.View view, string page_uri) {
/* Don't block ads on internal pages */
if (!Midori.URI.is_http (page_uri))
return;
if ("adblock:element" in (Environment.get_variable ("MIDORI_DEBUG") ?? ""))
debug_element = true;
else
debug_element = status_icon.debug_element_toggled;
#if USE_CSS_SELECTOR_FOR_BLOCKED_RESOURCES
string? blocked_css = get_hider_css_for_blocked_resources ();
if (blocked_css != null)
view.inject_stylesheet (blocked_css);
#else
string? blocked_js = get_hider_js_for_blocked_resorces ();
if (blocked_js != null)
view.execute_script (blocked_js, null);
#endif
string? style = get_hider_css_rules_for_uri (page_uri);
if (style != null)
view.inject_stylesheet (style);
}
internal void init () {
hider_selectors = new StringBuilder ();
load_config ();
manager = new SubscriptionManager (config);
status_icon = new StatusIcon (config, manager);
foreach (unowned Subscription sub in config) {
try {
sub.parse ();
} catch (GLib.Error error) {
warning ("Error parsing %s: %s", sub.uri, error.message);
}
}
config.notify["size"].connect (subscriptions_added_removed);
manager.description_label.activate_link.connect (open_link);
#if !USE_CSS_SELECTOR_FOR_BLOCKED_RESOURCES
js_hider_function_body = fetch_js_hider_function_body ();
#endif
}
bool open_link (string uri) {
var browser = get_app ().browser;
var view = browser.add_uri (uri);
browser.tab = view;
return true;
}
void subscriptions_added_removed (ParamSpec pspec) {
hider_selectors = new StringBuilder ();
}
void load_config () {
#if HAVE_WEBKIT2
string config_dir = Path.build_filename (GLib.Environment.get_user_config_dir (), PACKAGE_NAME, "extensions", "libadblock." + GLib.Module.SUFFIX);
Midori.Paths.mkdir_with_parents (config_dir);
#else
string config_dir = Midori.Paths.get_extension_config_dir ("adblock");
#endif
string presets = Midori.Paths.get_extension_preset_filename ("adblock", "config");
string filename = Path.build_filename (config_dir, "config");
config = new Config (filename, presets);
string custom_list = GLib.Path.build_filename (config_dir, "custom.list");
try {
custom = new Subscription (Filename.to_uri (custom_list, null));
custom.mutable = false;
custom.title = _("Custom");
config.add (custom);
} catch (Error error) {
custom = null;
warning ("Failed to add custom list %s: %s", custom_list, error.message);
}
}
public Adblock.Directive get_directive_for_uri (string request_uri, string page_uri) {
if (!config.enabled)
return Directive.ALLOW;
/* Always allow the main page */
if (request_uri == page_uri)
return Directive.ALLOW;
/* Skip adblock on internal pages */
if (Midori.URI.is_blank (page_uri))
return Directive.ALLOW;
/* Skip adblock on favicons and non http schemes */
if (!Midori.URI.is_http (request_uri) || request_uri.has_suffix ("favicon.ico"))
return Directive.ALLOW;
Directive? directive = null;
foreach (unowned Subscription sub in config) {
directive = sub.get_directive (request_uri, page_uri);
if (directive != null)
break;
}
if (directive == null)
directive = Directive.ALLOW;
else if (directive == Directive.BLOCK) {
status_icon.set_state (State.BLOCKED);
#if USE_CSS_SELECTOR_FOR_BLOCKED_RESOURCES
hider_selectors.append ("img[src*=\"%s\"] , iframe[src*=\"%s\"] , ".printf (request_uri, request_uri));
#else
hider_selectors.append (" uris.push ('%s');\n".printf (request_uri));
#endif
}
return directive;
}
internal bool request_handled (string request_uri, string page_uri) {
return get_directive_for_uri (request_uri, page_uri) == Directive.BLOCK;
}
}
static void debug (string format, ...) {
bool debug_match = "adblock:match" in (Environment.get_variable ("MIDORI_DEBUG") ?? "");
if (!debug_match)
return;
var args = va_list ();
stdout.vprintf (format + "\n", args);
}
internal static string? fixup_regex (string prefix, string? src) {
if (src == null)
return null;
var fixed = new StringBuilder ();
fixed.append(prefix);
uint i = 0;
if (src[0] == '*')
i++;
uint l = src.length;
while (i < l) {
char c = src[i];
switch (c) {
case '*':
fixed.append (".*"); break;
case '|':
case '^':
case '+':
break;
case '?':
case '[':
case ']':
case '.':
case '(':
case ')':
fixed.append_printf ("\\%c", c); break;
default:
fixed.append_c (c); break;
}
i++;
}
return fixed.str;
}
}
#if HAVE_WEBKIT2
#if !HAVE_WEBKIT2_3_91
Adblock.Extension? filter;
public static void webkit_web_extension_initialize (WebKit.WebExtension web_extension) {
filter = new Adblock.Extension.WebExtension (web_extension);
}
#endif
#endif
public Midori.Extension extension_init () {
return new Adblock.Extension ();
}
static string? tmp_folder = null;
string get_test_file (string contents) {
if (tmp_folder == null)
tmp_folder = Midori.Paths.make_tmp_dir ("adblockXXXXXX");
string checksum = Checksum.compute_for_string (ChecksumType.MD5, contents);
string file = Path.build_path (Path.DIR_SEPARATOR_S, tmp_folder, checksum);
try {
FileUtils.set_contents (file, contents, -1);
} catch (Error file_error) {
GLib.error (file_error.message);
}
return file;
}
struct TestCaseConfig {
public string content;
public uint size;
public bool enabled;
}
const TestCaseConfig[] configs = {
{ "", 0, true },
{ "[settings]", 0, true },
{ "[settings]\nfilters=foo;", 1, true },
{ "[settings]\nfilters=foo;\ndisabled=true", 1, false }
};
void test_adblock_config () {
assert (new Adblock.Config (null, null).size == 0);
foreach (var conf in configs) {
var config = new Adblock.Config (get_test_file (conf.content), null);
if (config.size != conf.size)
error ("Wrong size %s rather than %s:\n%s",
config.size.to_string (), conf.size.to_string (), conf.content);
if (config.enabled != conf.enabled)
error ("Wrongly got enabled=%s rather than %s:\n%s",
config.enabled.to_string (), conf.enabled.to_string (), conf.content);
}
}
struct TestCaseSub {
public string uri;
public bool active;
}
const TestCaseSub[] subs = {
{ "http://foo.com", true },
{ "http://bar.com", false },
{ "https://spam.com", true },
{ "https://eggs.com", false },
{ "file:///bla", true },
{ "file:///blub", false }
};
void test_adblock_subs () {
var config = new Adblock.Config (get_test_file ("""
[settings]
filters=http://foo.com;http-//bar.com;https://spam.com;http-://eggs.com;file:///bla;file-///blub;http://foo.com;
"""), null);
assert (config.enabled);
foreach (var sub in subs) {
bool found = false;
foreach (unowned Adblock.Subscription subscription in config) {
if (subscription.uri == sub.uri) {
assert (subscription.active == sub.active);
found = true;
}
}
if (!found)
error ("%s not found", sub.uri);
}
/* 6 unique URLs, 1 duplicate */
assert (config.size == 6);
/* Duplicates aren't added again either */
assert (!config.add (new Adblock.Subscription ("https://spam.com")));
/* Saving the config and loading it should give back identical results */
config.save ();
var copy = new Adblock.Config (config.path, null);
assert (copy.size == config.size);
assert (copy.enabled == config.enabled);
for (int i = 0; i < config.size; i++) {
assert (copy[i].uri == config[i].uri);
assert (copy[i].active == config[i].active);
}
/* Enabled status should be saved and loaded */
config.enabled = false;
copy = new Adblock.Config (config.path, null);
assert (copy.enabled == config.enabled);
/* Flipping individual active values should be retained after saving */
foreach (unowned Adblock.Subscription sub in config)
sub.active = !sub.active;
copy = new Adblock.Config (config.path, null);
for (uint i = 0; i < config.size; i++) {
if (config[i].active != copy[i].active) {
string contents;
try {
FileUtils.get_contents (config.path, out contents, null);
} catch (Error file_error) {
error (file_error.message);
}
error ("%s is %s but should be %s:\n%s",
copy[i].uri, copy[i].active ? "active" : "disabled", config[i].active ? "active" : "disabled", contents);
}
}
/* Adding and removing works, changes size */
var s = new Adblock.Subscription ("http://en.de");
assert (config.add (s));
assert (config.size == 7);
config.remove (s);
assert (config.size == 6);
/* If it was removed before we should be able to add it again */
assert (config.add (s));
assert (config.size == 7);
}
void test_adblock_init () {
/* No config */
var extension = new Adblock.Extension ();
extension.init ();
assert (extension.config.enabled);
/* Defaults plus custom */
if (extension.config.size != 3)
error ("Expected 3 initial subs, got %s".printf (
extension.config.size.to_string ()));
assert (extension.status_icon.state == Adblock.State.ENABLED);
/* Add new subscription */
string path = Midori.Paths.get_res_filename ("adblock.list");
string uri;
try {
uri = Filename.to_uri (path, null);
} catch (Error error) {
GLib.error (error.message);
}
var sub = new Adblock.Subscription (uri);
extension.config.add (sub);
assert (extension.status_icon.state == Adblock.State.ENABLED);
assert (extension.config.size == 4);
try {
sub.parse ();
} catch (GLib.Error error) {
GLib.error (error.message);
}
/* The page itself never hits */
assert (!extension.request_handled ("https://ads.bogus.name/blub", "https://ads.bogus.name/blub"));
/* Favicons don't either */
assert (!extension.request_handled ("https://foo.com", "https://ads.bogus.name/blub/favicon.ico"));
assert (extension.status_icon.state == Adblock.State.ENABLED);
/* Some sanity checks to be sure there's no earlier problem */
assert (sub.title == "Exercise");
assert (sub.get_directive ("https://ads.bogus.name/blub", "") == Adblock.Directive.BLOCK);
/* A rule hit should add to the cache */
assert (extension.request_handled ("https://ads.bogus.name/blub", "https://foo.com"));
assert (extension.status_icon.state == Adblock.State.BLOCKED);
assert (extension.hider_selectors.str != "");
/* Disabled means no request should be handled */
extension.config.enabled = false;
assert (!extension.request_handled ("https://ads.bogus.name/blub", "https://foo.com"));
// FIXME: assert (extension.status_icon.state == Adblock.State.DISABLED);
/* Removing a subscription should clear the cached CSS */
extension.config.remove (sub);
assert (extension.hider_selectors.str == "");
assert (extension.config.size == 3);
/* Now let's add a custom rule */
extension.config.enabled = true;
extension.custom.add_rule ("/adpage.");
assert (extension.custom.get_directive ("http://www.engadget.com/_uac/adpage.html", "http://foo.com") == Adblock.Directive.BLOCK);
assert (extension.request_handled ("http://www.engadget.com/_uac/adpage.html", "http://foo.com"));
assert (extension.status_icon.state == Adblock.State.BLOCKED);
/* Second attempt, from cache, same result */
assert (extension.custom.get_directive ("http://www.engadget.com/_uac/adpage.html", "http://foo.com") == Adblock.Directive.BLOCK);
assert (extension.request_handled ("http://www.engadget.com/_uac/adpage.html", "http://foo.com"));
/* Another custom rule */
extension.custom.add_rule ("/images/*.png");
assert (extension.custom.get_directive ("http://alpha.beta.com/images/yota.png", "https://foo.com") == Adblock.Directive.BLOCK);
assert (extension.request_handled ("http://alpha.beta.com/images/yota.png", "https://foo.com"));
/* Second attempt, from cache, same result */
assert (extension.request_handled ("http://alpha.beta.com/images/yota.png", "https://foo.com"));
/* Similar uri but .jpg should pass */
assert (!extension.request_handled ("http://alpha.beta.com/images/yota.jpg", "https://foo.com"));
assert (extension.custom.get_directive ("http://alpha.beta.com/images/yota.jpg", "https://foo.com") != Adblock.Directive.BLOCK);
/* Add whitelist rule */
extension.custom.add_rule ("@@http://alpha.beta.com/images/drop*bear.png");
assert (!extension.request_handled ("http://alpha.beta.com/images/drop-bear.png", "https://foo.com"));
assert (!extension.request_handled ("http://alpha.beta.com/images/dropzone_bear.png", "https://foo.com"));
assert (extension.custom.get_directive ("http://alpha.beta.com/images/drop-bear.png", "https://foo.com") != Adblock.Directive.BLOCK);
/* Doesn't match whitelist, matches *.png rule, should be blocked */
assert (extension.request_handled ("http://alpha.beta.com/images/bear.png", "https://foo.com"));
assert (extension.custom.get_directive ("http://alpha.beta.com/images/bear.png", "https://foo.com") == Adblock.Directive.BLOCK);
}
struct TestCaseLine {
public string line;
public string fixed;
}
const TestCaseLine[] lines = {
{ null, null },
{ "!", "!" },
{ "@@", "@@" },
{ "##", "##" },
{ "[", "\\[" },
{ "+advert/", "advert/" },
{ "*foo", "foo" },
{ "f*oo", "f.*oo" },
{ "?foo", "\\?foo" },
{ "foo?", "foo\\?" },
{ ".*foo/bar", "..*foo/bar" },
{ ".*.*.*.info/popup", "\\..*\\..*\\..*\\.info/popup" },
{ "http://bla.blub/*", "http://bla.blub/.*" },
{ "bag?r[]=*cpa", "bag\\?r\\[\\]=.*cpa" },
{ "(facebookLike,", "\\(facebookLike," }
};
void test_adblock_fixup_regexp () {
foreach (var line in lines) {
Katze.assert_str_equal (line.line, Adblock.fixup_regex ("", line.line), line.fixed);
}
}
struct TestCasePattern {
public string uri;
public Adblock.Directive directive;
}
const TestCasePattern[] patterns = {
{ "http://www.engadget.com/_uac/adpage.html", Adblock.Directive.BLOCK },
{ "http://test.dom/test?var=1", Adblock.Directive.BLOCK },
{ "http://ads.foo.bar/teddy", Adblock.Directive.BLOCK },
{ "http://ads.fuu.bar/teddy", Adblock.Directive.ALLOW },
{ "https://ads.bogus.name/blub", Adblock.Directive.BLOCK },
// FIXME { "http://ads.bla.blub/kitty", Adblock.Directive.BLOCK },
// FIXME { "http://ads.blub.boing/soda", Adblock.Directive.BLOCK },
{ "http://ads.foo.boing/beer", Adblock.Directive.ALLOW },
{ "https://testsub.engine.adct.ru/test?id=1", Adblock.Directive.BLOCK },
{ "http://test.ltd/addyn/test/test?var=adtech;&var2=1", Adblock.Directive.BLOCK },
{ "http://add.doubleclick.net/pfadx/aaaa.mtvi", Adblock.Directive.BLOCK },
{ "http://add.doubleclick.net/pfadx/aaaa.mtv", Adblock.Directive.ALLOW },
{ "http://objects.tremormedia.com/embed/xml/list.xml?r=", Adblock.Directive.BLOCK },
{ "http://qq.videostrip.c/sub/admatcherclient.php", Adblock.Directive.ALLOW },
{ "http://qq.videostrip.com/sub/admatcherclient.php", Adblock.Directive.BLOCK },
{ "http://qq.videostrip.com/sub/admatcherclient.php", Adblock.Directive.BLOCK },
{ "http://br.gcl.ru/cgi-bin/br/test", Adblock.Directive.BLOCK },
{ "https://bugs.webkit.org/buglist.cgi?query_format=advanced&short_desc_type=allwordssubstr&short_desc=&long_desc_type=substring&long_desc=&bug_file_loc_type=allwordssubstr&bug_file_loc=&keywords_type=allwords&keywords=&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailassigned_to1=1&emailtype1=substring&email1=&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailtype2=substring&email2=&bugidtype=include&bug_id=&votes=&chfieldfrom=&chfieldto=Now&chfieldvalue=&query_based_on=gtkport&field0-0-0=keywords&type0-0-0=anywordssubstr&value0-0-0=Gtk%20Cairo%20soup&field0-0-1=short_desc&type0-0-1=anywordssubstr&value0-0-1=Gtk%20Cairo%20soup%20autoconf%20automake%20autotool&field0-0-2=component&type0-0-2=equals&value0-0-2=WebKit%20Gtk", Adblock.Directive.ALLOW },
{ "http://www.engadget.com/2009/09/24/google-hits-android-rom-modder-with-a-cease-and-desist-letter/", Adblock.Directive.ALLOW },
{ "http://karibik-invest.com/es/bienes_raices/search.php?sqT=19&sqN=&sqMp=&sqL=0&qR=1&sqMb=&searchMode=1&action=B%FAsqueda", Adblock.Directive.ALLOW },
{ "http://google.com", Adblock.Directive.ALLOW }
};
string pretty_directive (Adblock.Directive? directive) {
if (directive == null)
return "none";
return directive.to_string ();
}
void test_adblock_pattern () {
string path = Midori.Paths.get_res_filename ("adblock.list");
string uri;
try {
uri = Filename.to_uri (path, null);
} catch (Error error) {
GLib.error (error.message);
}
var sub = new Adblock.Subscription (uri);
try {
sub.parse ();
} catch (Error error) {
GLib.error (error.message);
}
foreach (var pattern in patterns) {
Adblock.Directive? directive = sub.get_directive (pattern.uri, "");
if (directive == null)
directive = Adblock.Directive.ALLOW;
if (directive != pattern.directive) {
error ("%s expected for %s but got %s",
pretty_directive (pattern.directive), pattern.uri, pretty_directive (directive));
}
}
}
string pretty_date (DateTime? date) {
if (date == null)
return "N/A";
return date.to_string ();
}
struct TestUpdateExample {
public string content;
public bool result;
public bool valid;
}
const TestUpdateExample[] examples = {
{ "[Adblock Plus 1.1]\n! Last modified: 05 Sep 2010 11:00 UTC\n! This list expires after 48 hours\n", true, true },
{ "[Adblock Plus 1.1]\n! Last modified: 05.09.2010 11:00 UTC\n! Expires: 2 days (update frequency)\n", true, true },
{ "[Adblock Plus 1.1]\n! Updated: 05 Nov 2024 11:00 UTC\n! Expires: 5 days (update frequency)\n", false, true },
{ "[Adblock]\n! dutchblock v3\n! This list expires after 14 days\n|http://b*.mookie1.com/\n", false, true },
{ "[Adblock Plus 2.0]\n! Last modification time (GMT): 2012.11.05 13:33\n! Expires: 5 days (update frequency)\n", true, true },
{ "[Adblock Plus 2.0]\n! Last modification time (GMT): 2012.11.05 13:33\n", true, true },
{ "[Adblock]\n ! dummy, i dont have any dates\n", false, true },
/* non-standard update time metadata as found in http://abp.mozilla-hispano.org/nauscopio/filtros.txt */
{ "[Adblock Plus 2.0]\n ! Last modified: Oct 26, 2013 18:00 UTC\n ! This list expires after 5 days\n! Last modified by maty: 12Oct2013\n! \n", false, true },
{ "\n", false, false }
};
void test_subscription_update () {
string uri;
FileIOStream iostream;
File file;
try {
file = File.new_tmp ("midori_adblock_update_test_XXXXXX", out iostream);
uri = file.get_uri ();
} catch (Error error) {
GLib.error (error.message);
}
var sub = new Adblock.Subscription (uri);
var updater = new Adblock.Updater ();
sub.add_feature (updater);
foreach (var example in examples) {
try {
file.replace_contents (example.content.data, null, false, FileCreateFlags.NONE, null);
sub.clear ();
sub.parse ();
} catch (Error error) {
GLib.error (error.message);
}
if (example.valid != sub.valid)
error ("Subscription expected to be %svalid but %svalid:\n%s",
example.valid ? "" : "in", sub.valid ? "" : "in", example.content);
if (example.result != updater.needs_update)
error ("Update%s expected for:\n%s\nLast Updated: %s\nExpires: %s",
example.result ? "" : " not", example.content,
pretty_date (updater.last_updated), pretty_date (updater.expires));
}
}
struct TestSubUri {
public string? src_uri;
public string? dst_uri;
}
const TestSubUri[] suburis =
{
{ null, null },
{ "not-a-link", null },
{ "http://some.uri", "http://some.uri" },
{ "abp:subscribe?location=https%3A%2F%2Feasylist-downloads.adblockplus.org%2Fabpindo%2Beasylist.txt&title=ABPindo%2BEasyList", "https://easylist-downloads.adblockplus.org/abpindo+easylist.txt" }
};
void test_subscription_uri_parsing () {
string? parsed_uri;
foreach (var example in suburis) {
parsed_uri = Adblock.parse_subscription_uri (example.src_uri);
if (parsed_uri != example.dst_uri)
error ("Subscription expected to be %svalid but %svalid:\n%s",
example.dst_uri, parsed_uri, example.src_uri);
}
}
public void extension_test () {
Test.add_func ("/extensions/adblock2/config", test_adblock_config);
Test.add_func ("/extensions/adblock2/subs", test_adblock_subs);
Test.add_func ("/extensions/adblock2/init", test_adblock_init);
Test.add_func ("/extensions/adblock2/parse", test_adblock_fixup_regexp);
Test.add_func ("/extensions/adblock2/pattern", test_adblock_pattern);
Test.add_func ("/extensions/adblock2/update", test_subscription_update);
Test.add_func ("/extensions/adblock2/subsparse", test_subscription_uri_parsing);
}

View file

@ -0,0 +1,52 @@
/*
Copyright (C) 2009-2014 Christian Dywan <christian@twotoasts.de>
Copyright (C) 2009-2012 Alexander Butenko <a.butenka@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
namespace Adblock {
public abstract class Filter : Feature {
Options optslist;
protected HashTable<string, Regex?> rules;
public virtual void insert (string sig, Regex regex) {
rules.insert (sig, regex);
}
public virtual Regex? lookup (string sig) {
return rules.lookup (sig);
}
public virtual uint size () {
return rules.size ();
}
protected Filter (Options options) {
optslist = options;
clear ();
}
public override void clear () {
rules = new HashTable<string, Regex> (str_hash, str_equal);
}
protected bool check_rule (Regex regex, string pattern, string request_uri, string page_uri) throws Error {
if (!regex.match_full (request_uri))
return false;
var opts = optslist.lookup (pattern);
if (opts != null && Regex.match_simple (",third-party", opts,
RegexCompileFlags.CASELESS, RegexMatchFlags.NOTEMPTY))
if (page_uri != null && regex.match_full (page_uri))
return false;
debug ("blocked by pattern regexp=%s -- %s", regex.get_pattern (), request_uri);
return true;
}
}
}

View file

@ -0,0 +1,47 @@
/*
Copyright (C) 2009-2014 Christian Dywan <christian@twotoasts.de>
Copyright (C) 2009-2012 Alexander Butenko <a.butenka@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
namespace Adblock {
public class Keys : Filter {
List<Regex> blacklist;
public Keys (Options options) {
base (options);
}
public override void clear () {
base.clear ();
blacklist = new List<Regex> ();
}
public override Directive? match (string request_uri, string page_uri) throws Error {
string? uri = fixup_regex ("", request_uri);
if (uri == null)
return null;
int signature_size = 8;
int pos, l = uri.length;
for (pos = l - signature_size; pos >= 0; pos--) {
string signature = uri.offset (pos).ndup (signature_size);
var regex = rules.lookup (signature);
if (regex == null || blacklist.find (regex) != null)
continue;
if (check_rule (regex, uri, request_uri, page_uri))
return Directive.BLOCK;
blacklist.prepend (regex);
}
return null;
}
}
}

View file

@ -0,0 +1,32 @@
/*
Copyright (C) 2014 Christian Dywan <christian@twotoasts.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
namespace Adblock {
public class Options : GLib.Object {
HashTable<string, string?> optslist;
public Options () {
clear ();
}
public void insert (string sig, string? opts) {
optslist.insert (sig, opts);
}
public string? lookup (string sig) {
return optslist.lookup (sig);
}
public void clear () {
optslist = new HashTable<string, string?> (str_hash, str_equal);
}
}
}

View file

@ -0,0 +1,26 @@
/*
Copyright (C) 2009-2014 Christian Dywan <christian@twotoasts.de>
Copyright (C) 2009-2012 Alexander Butenko <a.butenka@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
namespace Adblock {
public class Pattern : Filter {
public Pattern (Options options) {
base (options);
}
public override Directive? match (string request_uri, string page_uri) throws Error {
foreach (unowned string patt in rules.get_keys ())
if (check_rule (rules.lookup (patt), patt, request_uri, page_uri))
return Directive.BLOCK;
return null;
}
}
}

View file

@ -0,0 +1,404 @@
/*
Copyright (C) 2009-2014 Christian Dywan <christian@twotoasts.de>
Copyright (C) 2009-2012 Alexander Butenko <a.butenka@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
namespace Adblock {
public abstract class Feature : GLib.Object {
public virtual bool header (string key, string value) {
return false;
}
public virtual bool parsed (File file) {
return true;
}
public virtual Directive? match (string request_uri, string page_uri) throws Error {
return null;
}
public virtual void clear () {
}
}
public class Subscription : GLib.Object {
public string? path;
bool debug_parse;
public string uri { get; set; default = null; }
public string title { get; set; default = null; }
public bool active { get; set; default = true; }
public bool mutable { get; set; default = true; }
public bool valid { get; private set; default = true; }
HashTable<string, Directive?> cache;
List<Feature> features;
public Pattern pattern;
public Keys keys;
public Options optslist;
public Whitelist whitelist;
public Element element;
#if !HAVE_WEBKIT2
WebKit.Download? download;
#endif
public Subscription (string uri) {
debug_parse = "adblock:parse" in (Environment.get_variable ("MIDORI_DEBUG") ?? "");
this.uri = uri;
this.optslist = new Options ();
this.whitelist = new Whitelist (optslist);
add_feature (this.whitelist);
this.keys = new Keys (optslist);
add_feature (this.keys);
this.pattern = new Pattern (optslist);
add_feature (this.pattern);
this.element = new Element ();
add_feature (this.element);
clear ();
}
public void add_feature (Feature feature) {
features.append (feature);
size++;
}
/* foreach support */
public new unowned Feature? get (uint index) {
return features.nth_data (index);
}
public uint size { get; private set; }
public void clear () {
cache = new HashTable<string, Directive?> (str_hash, str_equal);
foreach (unowned Feature feature in features)
feature.clear ();
optslist.clear ();
}
internal void parse_line (string? line) throws Error {
if (line.has_prefix ("@@")) {
if (line.contains("$") && line.contains ("domain"))
return;
if (line.has_prefix ("@@||"))
add_url_pattern ("^", "whitelist", line.offset (4));
else if (line.has_prefix ("@@|"))
add_url_pattern ("^", "whitelist", line.offset (3));
else
add_url_pattern ("", "whitelist", line.offset (2));
return;
}
/* TODO: [include] [exclude] */
if (line[0] == '[')
return;
/* CSS block hider */
if (line.has_prefix ("##")) {
/* TODO */
return;
}
if (line[0] == '#')
return;
/* TODO: CSS hider whitelist */
if ("#@#" in line)
return;
/* Per domain CSS hider rule */
if ("##" in line) {
frame_add_private (line, "##");
return;
}
if ("#" in line) {
frame_add_private (line, "#");
return;
}
/* URL blocker rule */
if (line.has_prefix ("|")) {
/* TODO: handle options and domains excludes */
if (line.contains("$"))
return;
if (line.has_prefix ("||"))
add_url_pattern ("", "fulluri", line.offset (2));
else
add_url_pattern ("^", "fulluri", line.offset (1));
return /* add_url_pattern */;
}
add_url_pattern ("", "uri", line);
return /* add_url_pattern */;
}
void frame_add_private (string line, string sep) {
string[] data = line.split (sep, 2);
if (!(data[1] != null && data[1] != "")
|| data[1].chr (-1, '\'') != null
|| (data[1].chr (-1, ':') != null
&& !Regex.match_simple (".*\\[.*:.*\\].*", data[1],
RegexCompileFlags.CASELESS, RegexMatchFlags.NOTEMPTY))) {
return;
}
if (data[0].chr (-1, ',') != null) {
string[] domains = data[0].split (",", -1);
foreach (unowned string domain in domains) {
/* Ignore Firefox-specific option */
if (domain == "~pregecko2")
continue;
string stripped = domain.strip ();
/* FIXME: ~ should negate match */
if (stripped[0] == '~')
stripped = stripped.substring (1, -1);
update_css_hash (stripped, data[1]);
}
}
else {
update_css_hash (data[0], data[1]);
}
}
bool css_element_seems_valid (string element) {
bool is_valid = true;
string[] valid_elements = { "::after", "::before", "a", "abbr", "address", "article", "aside",
"b", "blockquote", "caption", "center", "cite", "code", "div", "dl", "dt", "dd", "em",
"feed", "fieldset", "figcaption", "figure", "font", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6",
"header", "hgroup", "i", "iframe", "iframe html *", "img", "kbd", "label", "legend", "li",
"m", "main", "marquee", "menu", "nav", "ol", "option", "p", "pre", "q", "samp", "section",
"small", "span", "strong", "summary", "table", "tr", "tbody", "td", "th", "thead", "tt", "ul" };
if (!element.has_prefix (".") && !element.has_prefix ("#")
&& !(element.split("[")[0] in valid_elements))
is_valid = false;
bool debug_selectors = "adblock:css" in (Environment.get_variable ("MIDORI_DEBUG") ?? "");
if (debug_selectors)
stdout.printf ("Adblock '%s' %s: %s\n",
this.title, is_valid ? "selector" : "INVALID?", element);
return is_valid;
}
void update_css_hash (string domain, string value) {
if (css_element_seems_valid (value)) {
string? olddata = element.lookup (domain);
if (olddata != null) {
string newdata = olddata + " , " + value;
element.insert (domain, newdata);
} else {
element.insert (domain, value);
}
}
}
void add_url_pattern (string prefix, string type, string line) throws Error {
string[]? data = line.split ("$", 2);
if (data == null || data[0] == null)
return;
string patt, opts;
patt = data[0];
opts = type;
if (data[1] != null)
opts = type + "," + data[1];
if (Regex.match_simple ("subdocument", opts,
RegexCompileFlags.CASELESS, RegexMatchFlags.NOTEMPTY))
return;
string format_patt = fixup_regex (prefix, patt);
if (debug_parse)
stdout.printf ("got: %s opts %s\n", format_patt, opts);
compile_regexp (format_patt, opts);
/* return format_patt */
}
bool compile_regexp (string? patt, string opts) throws Error {
if (patt == null)
return false;
try {
var regex = new Regex (patt, RegexCompileFlags.OPTIMIZE, RegexMatchFlags.NOTEMPTY);
/* is pattern is already a regex? */
if (Regex.match_simple ("^/.*[\\^\\$\\*].*/$", patt,
RegexCompileFlags.UNGREEDY, RegexMatchFlags.NOTEMPTY)
|| (opts != null && opts.contains ("whitelist"))) {
if (debug_parse)
stdout.printf ("patt: %s\n", patt);
if (opts.contains ("whitelist"))
this.whitelist.insert (patt, regex);
else
this.pattern.insert (patt, regex);
this.optslist.insert (patt, opts);
return false;
} else { /* nope, no regex */
int pos = 0, len;
int signature_size = 8;
string sig;
len = patt.length;
/* chop up pattern into substrings for faster matching */
for (pos = len - signature_size; pos>=0; pos--)
{
sig = patt.offset (pos).ndup (signature_size);
/* we don't have a * nor \\, does not look like regex, save chunk as "key" */
if (!Regex.match_simple ("[\\*]", sig, RegexCompileFlags.UNGREEDY, RegexMatchFlags.NOTEMPTY) && keys.lookup (sig) == null) {
this.keys.insert (sig, regex);
this.optslist.insert (sig, opts);
} else {
/* starts with * or \\ - save as regex */
if ((sig.has_prefix ("*") || sig.has_prefix("\\")) && this.pattern.lookup (sig) == null) {
this.pattern.insert (sig, regex);
this.optslist.insert (sig, opts);
}
}
}
}
return false;
}
catch (Error error) {
warning ("Adblock compile regexp: %s", error.message);
return true;
}
}
public void parse_header (string header) throws Error {
/* Headers come in two forms
! Foo: Bar
! Some freeform text
*/
string key = header;
string value = "";
if (header.contains (":")) {
string[] parts = header.split (":", 2);
if (parts[0] != null && parts[0] != ""
&& parts[1] != null && parts[1] != "") {
key = parts[0].substring (2, -1);
value = parts[1].substring (1, -1);
}
}
debug ("Header '%s' says '%s'", key, value);
if (key == "Title")
title = value;
foreach (unowned Feature feature in features) {
if (feature.header (key, value))
break;
}
}
#if !HAVE_WEBKIT2
void download_status (ParamSpec pspec) {
if (download.get_status () != WebKit.DownloadStatus.FINISHED)
return;
download = null;
try {
parse ();
} catch (Error error) {
warning ("Error parsing %s: %s", uri, error.message);
}
}
#endif
public void parse () throws Error
{
if (!active)
return;
debug ("Parsing %s (%s)", uri, path);
clear ();
if (uri.has_prefix ("file://"))
path = Filename.from_uri (uri);
else {
string cache_dir = GLib.Path.build_filename (GLib.Environment.get_user_cache_dir (), PACKAGE_NAME, "adblock");
Midori.Paths.mkdir_with_parents (cache_dir);
string filename = Checksum.compute_for_string (ChecksumType.MD5, this.uri, -1);
path = GLib.Path.build_filename (cache_dir, filename);
}
File filter_file = File.new_for_path (path);
DataInputStream stream;
try {
stream = new DataInputStream (filter_file.read ());
} catch (IOError.NOT_FOUND exist_error) {
#if HAVE_WEBKIT2
/* TODO */
#else
/* Don't bother trying to download local files */
if (!uri.has_prefix ("file://")) {
if (download != null)
return;
string destination_uri = Filename.to_uri (path, null);
debug ("Fetching %s to %s now", uri, destination_uri);
download = new WebKit.Download (new WebKit.NetworkRequest (uri));
if (!Midori.Download.has_enough_space (download, destination_uri, true))
throw new FileError.EXIST ("Can't download to \"%s\"", path);
download.destination_uri = destination_uri;
download.notify["status"].connect (download_status);
download.start ();
}
#endif
return;
}
valid = false;
string? line;
while ((line = stream.read_line (null)) != null) {
if (line == null)
continue;
string chomped = line.chomp ();
if (chomped == "")
continue;
if (line[0] == '!')
parse_header (chomped);
else
parse_line (chomped);
/* The file isn't completely empty */
valid = true;
}
foreach (unowned Feature feature in features) {
if (!feature.parsed (filter_file))
valid = false;
}
}
public Directive? get_directive (string request_uri, string page_uri) {
try {
Directive? directive = cache.lookup (request_uri);
if (directive != null)
return directive;
foreach (unowned Feature feature in features) {
directive = feature.match (request_uri, page_uri);
if (directive != null) {
debug ("%s gave %s for %s (%s)\n",
feature.get_type ().name (), directive.to_string (), request_uri, page_uri);
return directive;
}
}
} catch (Error error) {
warning ("Adblock match error: %s\n", error.message);
}
return null;
}
public void add_rule (string rule) {
try {
var file = File.new_for_uri (uri);
file.append_to (FileCreateFlags.NONE).write (("%s\n".printf (rule)).data);
parse ();
} catch (Error error) {
warning ("Failed to add custom rule: %s", error.message);
}
}
}
}

View file

@ -0,0 +1,155 @@
/*
Copyright (C) 2014 Paweł Forysiuk <tuxator@o2.pl>
Copyright (C) 2014 Christian Dywan <christian@twotoasts.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
namespace Adblock {
public class Updater : Feature {
string expires_meta;
string last_mod_meta;
public DateTime last_updated { get; set; }
public DateTime expires { get; set; }
public bool needs_update { get; set; }
public Updater () {
}
public override void clear () {
expires_meta = null;
last_mod_meta = null;
last_updated = null;
expires = null;
needs_update = false;
}
public override bool header (string key, string value) {
if (key.has_prefix ("Last mod") || key == "Updated") {
last_mod_meta = value;
return true;
} else if (key == "Expires") {
/* ! Expires: 5 days (update frequency) */
expires_meta = value;
return true;
} else if (key.has_prefix ("! This list expires after")) {
/* ! This list expires after 14 days */
expires_meta = key.substring (26, -1);
return true;
}
return false;
}
public override bool parsed (File file) {
process_dates (file);
/* It's not an error to have no update headers, we go for defaults */
return true;
}
int get_month_from_string (string? month) {
if (month == null)
return 0;
string[] months = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
for (int i = 0; i<= months.length; i++)
{
if (month.has_prefix (months[i]))
return i+1;
}
return 0;
}
void process_dates (File file) {
DateTime now = new DateTime.now_local ();
last_updated = null;
expires = null;
/* We have "last modification" metadata */
if (last_mod_meta != null && (last_mod_meta.contains (" ") && last_mod_meta[0].isdigit () == true)) {
int h = 0, min = 0, d, m, y;
/* Date in a form of: 20.08.2012 12:34 */
if (last_mod_meta.contains (".") || last_mod_meta.contains("-")) {
string[] parts = last_mod_meta.split (" ", 2);
string[] date_parts;
string split_char = " ";
/* contains time part ? */
if (parts[1] != "" && parts[1].contains (":")) {
string[] time_parts = parts[1].split (":", 2);
h = int.parse(time_parts[0]);
min = int.parse(time_parts[1]);
}
/* check if dot or dash was used as a delimiter */
if (parts[0].contains ("."))
split_char = ".";
else if (parts[0].contains ("-"))
split_char = "-";
date_parts = parts[0].split (split_char, 3);
m = int.parse(date_parts[1]);
if (date_parts[2].length == 4) {
y = int.parse(date_parts[2]);
d = int.parse(date_parts[0]);
} else {
y = int.parse(date_parts[0]);
d = int.parse(date_parts[2]);
}
} else { /* Date in a form of: 20 Mar 2012 12:34 */
string[] parts = last_mod_meta.split (" ", 4);
/* contains time part ? */
if (parts[3] != null && parts[3].contains (":")) {
string[] time_parts = parts[3].split (":", 2);
h = int.parse(time_parts[0]);
min = int.parse(time_parts[1]);
}
m = get_month_from_string (parts[1]);
if (parts[2].length == 4) {
y = int.parse(parts[2]);
d = int.parse(parts[0]);
} else {
y = int.parse(parts[0]);
d = int.parse(parts[2]);
}
}
last_updated = new DateTime.local (y, m, d, h, min, 0.0);
} else {
/* FIXME: use file modification date if there's no update header
try {
string modified = FileAttribute.TIME_MODIFIED;
var info = file.query_filesystem_info (modified);
last_updated = new DateTime.from_timeval_local (info.get_modification_time ());
} catch (Error error) {
last_updated = now;
}
*/
last_updated = now;
}
/* We have "expires" metadata */
if (expires_meta != null) {
if (expires_meta.contains ("days")) {
string[] parts = expires_meta.split (" ");
expires = last_updated.add_days (int.parse (parts[0]));
} else if (expires_meta.contains ("hours")) {
string[] parts = expires_meta.split (" ");
expires = last_updated.add_hours (int.parse (parts[0]));
}
} else {
/* No expire metadata found, assume x days */
int days_to_expire = 7;
expires = last_updated.add_days (days_to_expire);
}
/* Check if we are past expire date */
needs_update = now.compare (expires) == 1;
}
}
}

View file

@ -0,0 +1,30 @@
/*
Copyright (C) 2014 Christian Dywan <christian@twotoasts.de>
Copyright (C) 2014 Paweł Forysiuk <tuxator@o2.pl>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
namespace Adblock {
public class Whitelist : Filter {
public Whitelist (Options options) {
base (options);
}
public override Directive? match (string request_uri, string page_uri) throws Error {
foreach (unowned string white in rules.get_keys ()) {
var regex = rules.lookup (white);
if (!regex.match_full (request_uri))
return null;
if (Regex.match_simple (regex.get_pattern (), request_uri, RegexCompileFlags.UNGREEDY, RegexMatchFlags.NOTEMPTY))
return Directive.ALLOW;
}
return null;
}
}
}

View file

@ -0,0 +1,292 @@
/*
Copyright (C) 2009-2014 Christian Dywan <christian@twotoasts.de>
Copyright (C) 2009-2012 Alexander Butenko <a.butenka@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
namespace Adblock {
public class StatusIcon : Midori.ContextAction {
Config config;
SubscriptionManager manager;
public State state;
public bool debug_element_toggled;
public StatusIcon (Adblock.Config config, SubscriptionManager manager) {
GLib.Object (name: "AdblockStatusMenu");
this.config = config;
this.manager = manager;
this.debug_element_toggled = false;
var item = new Midori.ContextAction ("Preferences",
_("Preferences"), null, Gtk.STOCK_PREFERENCES);
item.activate.connect (() => {
manager.add_subscription (null);
});
add (item);
add (null);
var checkitem = new Gtk.ToggleAction ("Disable", _("Disable"), null, null);
checkitem.set_active (!config.enabled);
checkitem.toggled.connect (() => {
config.enabled = !checkitem.active;
set_state (config.enabled ? Adblock.State.ENABLED : Adblock.State.DISABLED);
});
add (checkitem);
var hideritem = new Gtk.ToggleAction ("HiddenElements",
_("Display hidden elements"), null, null);
hideritem.set_active (debug_element_toggled);
hideritem.toggled.connect (() => {
this.debug_element_toggled = hideritem.active;
});
add (hideritem);
set_status (config.enabled ? "enabled" : "disabled");
}
void set_status (string status) {
gicon = new GLib.ThemedIcon ("adblock-%s".printf (status));
}
public void set_state (Adblock.State state) {
this.state = state;
if (this.state == State.BLOCKED) {
set_status ("blocked");
tooltip = _("Blocking");
} else if (this.state == State.ENABLED) {
set_status ("enabled");
tooltip = _("Enabled");
} else if (this.state == State.DISABLED) {
set_status ("disabled");
tooltip = _("Disabled");
} else
assert_not_reached ();
}
}
public class SubscriptionManager {
Gtk.TreeView treeview;
Gtk.ListStore liststore;
Adblock.Config config;
public Gtk.Label description_label;
string description;
public SubscriptionManager (Config config) {
this.config = config;
this.liststore = new Gtk.ListStore (1, typeof (Subscription));
this.description_label = new Gtk.Label (null);
this.description = _("Type the address of a preconfigured filter list in the text entry and hit Enter.\n");
this.description += _("You can find more lists by visiting following sites:\n %s, %s\n".printf (
"<a href=\"http://adblockplus.org/en/subscriptions\">adblockplus.org/en/subscriptions</a>",
"<a href=\"http://easylist.adblockplus.org/\">easylist.adblockplus.org</a>"
));
}
public void add_subscription (string? uri) {
var dialog = new Gtk.Dialog.with_buttons (_("Configure Advertisement filters"),
null,
#if !HAVE_GTK3
Gtk.DialogFlags.NO_SEPARATOR |
#endif
Gtk.DialogFlags.DESTROY_WITH_PARENT,
Gtk.STOCK_HELP, Gtk.ResponseType.HELP,
Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE);
#if HAVE_GTK3
dialog.get_widget_for_response (Gtk.ResponseType.HELP).get_style_context ().add_class ("help_button");
#endif
dialog.set_icon_name (Gtk.STOCK_PROPERTIES);
dialog.set_response_sensitive (Gtk.ResponseType.HELP, false);
var hbox = new Gtk.HBox (false, 0);
(dialog.get_content_area () as Gtk.Box).pack_start (hbox, true, true, 12);
var vbox = new Gtk.VBox (false, 0);
hbox.pack_start (vbox, true, true, 4);
this.description_label.set_markup (this.description);
this.description_label.set_line_wrap (true);
vbox.pack_start (this.description_label, false, false, 4);
var entry = new Gtk.Entry ();
if (uri != null)
entry.set_text (uri);
vbox.pack_start (entry, false, false, 4);
liststore = new Gtk.ListStore (1, typeof (Subscription));
treeview = new Gtk.TreeView.with_model (liststore);
treeview.set_headers_visible (false);
var column = new Gtk.TreeViewColumn ();
var renderer_toggle = new Gtk.CellRendererToggle ();
column.pack_start (renderer_toggle, false);
column.set_cell_data_func (renderer_toggle, (column, renderer, model, iter) => {
Subscription sub;
liststore.get (iter, 0, out sub);
renderer.set ("active", sub.active,
"sensitive", sub.mutable);
});
renderer_toggle.toggled.connect ((path) => {
Gtk.TreeIter iter;
if (liststore.get_iter_from_string (out iter, path)) {
Subscription sub;
liststore.get (iter, 0, out sub);
sub.active = !sub.active;
}
});
treeview.append_column (column);
column = new Gtk.TreeViewColumn ();
var renderer_text = new Gtk.CellRendererText ();
column.pack_start (renderer_text, false);
renderer_text.set ("editable", true);
// TODO: renderer_text.edited.connect
column.set_cell_data_func (renderer_text, (column, renderer, model, iter) => {
Subscription sub;
liststore.get (iter, 0, out sub);
string status = "";
foreach (unowned Feature feature in sub) {
var updater = feature as Adblock.Updater;
if (updater != null) {
if (updater.last_updated != null)
status = updater.last_updated.format (_("Last update: %x %X"));
}
}
if (!sub.valid)
status = _("File incomplete - broken download?");
renderer.set ("markup", (Markup.printf_escaped ("<b>%s</b>\n%s",
sub.title ?? sub.uri, status)));
});
treeview.append_column (column);
column = new Gtk.TreeViewColumn ();
Gtk.CellRendererPixbuf renderer_button = new Gtk.CellRendererPixbuf ();
column.pack_start (renderer_button, false);
column.set_cell_data_func (renderer_button, on_render_button);
treeview.append_column (column);
var scrolled = new Gtk.ScrolledWindow (null, null);
scrolled.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
scrolled.add (treeview);
vbox.pack_start (scrolled);
int height;
treeview.create_pango_layout ("a\nb").get_pixel_size (null, out height);
scrolled.set_size_request (-1, height * 5);
foreach (unowned Subscription sub in config)
liststore.insert_with_values (null, 0, 0, sub);
treeview.button_release_event.connect (button_released);
entry.activate.connect (() => {
string? parsed_uri = Adblock.parse_subscription_uri (entry.text);
if (parsed_uri != null) {
var sub = new Subscription (parsed_uri);
if (config.add (sub)) {
liststore.insert_with_values (null, 0, 0, sub);
try {
sub.parse ();
} catch (GLib.Error error) {
warning ("Error parsing %s: %s", sub.uri, error.message);
}
}
}
entry.text = "";
});
dialog.get_content_area ().show_all ();
dialog.response.connect ((response)=>{ dialog.destroy (); });
dialog.show ();
}
void on_render_button (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
Subscription sub;
liststore.get (iter, 0, out sub);
renderer.set ("stock-id", sub.mutable ? Gtk.STOCK_DELETE : null,
"stock-size", Gtk.IconSize.MENU);
}
public bool button_released (Gdk.EventButton event) {
Gtk.TreePath? path;
Gtk.TreeViewColumn column;
if (treeview.get_path_at_pos ((int)event.x, (int)event.y, out path, out column, null, null)) {
if (path != null) {
if (column == treeview.get_column (2)) {
Gtk.TreeIter iter;
if (liststore.get_iter (out iter, path)) {
Subscription sub;
liststore.get (iter, 0, out sub);
if (sub.mutable) {
config.remove (sub);
liststore.remove (iter);
return true;
}
}
}
}
}
return false;
}
}
class CustomRulesEditor {
Gtk.Dialog dialog;
Subscription custom;
public string? rule { get; set; }
public CustomRulesEditor (Subscription custom) {
this.custom = custom;
}
public void set_uri (string uri) {
this.rule = uri;
}
public void show () {
this.dialog = new Gtk.Dialog.with_buttons (_("Edit rule"),
null,
#if !HAVE_GTK3
Gtk.DialogFlags.NO_SEPARATOR |
#endif
Gtk.DialogFlags.DESTROY_WITH_PARENT,
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_ADD, Gtk.ResponseType.ACCEPT);
dialog.set_icon_name (Gtk.STOCK_ADD);
dialog.resizable = false;
var hbox = new Gtk.HBox (false, 8);
var sizegroup = new Gtk.SizeGroup (Gtk.SizeGroupMode.HORIZONTAL);
hbox.border_width = 5;
var label = new Gtk.Label.with_mnemonic (_("_Rule:"));
sizegroup.add_widget (label);
hbox.pack_start (label, false, false, 0);
(dialog.get_content_area () as Gtk.Box).pack_start (hbox, false, true, 0);
var entry = new Gtk.Entry ();
sizegroup.add_widget (entry);
entry.activates_default = true;
entry.set_text (this.rule);
hbox.pack_start (entry, true, true, 0);
dialog.get_content_area ().show_all ();
dialog.set_default_response (Gtk.ResponseType.ACCEPT);
if (dialog.run () != Gtk.ResponseType.ACCEPT)
return;
this.rule = entry.get_text ();
this.dialog.destroy ();
custom.add_rule (this.rule);
}
}
}

View file

@ -393,6 +393,9 @@ addons_button_delete_clicked_cb (GtkWidget* toolitem,
{
GtkTreeModel* model;
GtkTreeIter iter;
GtkTreePath* path;
GtkTreeRowReference* row;
gchar* fullpath;
if (katze_tree_view_get_selected_iter (GTK_TREE_VIEW (addons->treeview),
&model, &iter))
@ -403,6 +406,11 @@ addons_button_delete_clicked_cb (GtkWidget* toolitem,
gchar* markup;
gtk_tree_model_get (model, &iter, 0, &element, -1);
fullpath = g_strdup (element->fullpath);
path = gtk_tree_model_get_path (model, &iter);
row = gtk_tree_row_reference_new (model, path);
gtk_tree_path_free (path);
dialog = gtk_message_dialog_new (
GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (addons))),
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
@ -424,6 +432,9 @@ addons_button_delete_clicked_cb (GtkWidget* toolitem,
GTK_MESSAGE_DIALOG (dialog), "%s", markup);
g_free (markup);
/* The execution of gtk_dialog_run allows the directory watcher to
rebuild the treeview and the element list, so our references may be
invalid afterward */
delete_response = gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (GTK_WIDGET (dialog));
@ -433,7 +444,7 @@ addons_button_delete_clicked_cb (GtkWidget* toolitem,
GFile* file;
gboolean result;
file = g_file_new_for_path (element->fullpath);
file = g_file_new_for_path (fullpath);
result = g_file_delete (file, NULL, &error);
if (!result && error)
@ -452,11 +463,20 @@ addons_button_delete_clicked_cb (GtkWidget* toolitem,
g_error_free (error);
}
if (result)
/* The row reference may have been invalidated if the
filesystem watcher deleted the row concurrently */
if (result && gtk_tree_row_reference_valid (row))
{
path = gtk_tree_row_reference_get_path (row);
gtk_tree_model_get_iter (model, &iter, path);
gtk_tree_path_free (path);
gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
}
gtk_tree_row_reference_free (row);
g_object_unref (file);
}
g_free (fullpath);
}
}
static void
@ -485,8 +505,8 @@ addons_open_in_editor_clicked_cb (GtkWidget* toolitem,
else
{
gchar* element_uri = g_filename_to_uri (element->fullpath, NULL, NULL);
sokoke_show_uri (NULL, element_uri,
gtk_get_current_event_time (), NULL);
gboolean handled = FALSE;
g_signal_emit_by_name (midori_browser_get_current_tab (browser), "open-uri", element_uri, &handled);
g_free (element_uri);
}
@ -522,8 +542,9 @@ addons_open_target_folder_clicked_cb (GtkWidget* toolitem,
folder_uri = g_filename_to_uri (folder, NULL, NULL);
g_free (folder);
sokoke_show_uri (gtk_widget_get_screen (GTK_WIDGET (addons->treeview)),
folder_uri, gtk_get_current_event_time (), NULL);
MidoriBrowser* browser = midori_browser_get_for_widget (addons->treeview);
gboolean handled = FALSE;
g_signal_emit_by_name (midori_browser_get_current_tab (browser), "open-uri", folder_uri, &handled);
g_free (folder_uri);
}
@ -617,7 +638,6 @@ addons_get_toolbar (MidoriViewable* viewable)
if (!ADDONS (viewable)->toolbar)
{
toolbar = gtk_toolbar_new ();
gtk_toolbar_set_icon_size (GTK_TOOLBAR (toolbar), GTK_ICON_SIZE_BUTTON);
toolitem = gtk_tool_item_new ();
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), toolitem, -1);
gtk_widget_show (GTK_WIDGET (toolitem));
@ -1044,7 +1064,7 @@ css_metadata_from_file (const gchar* filename,
domain = g_strndup (value + begin, end - begin * 2);
if (!midori_uri_is_location (domain)
&& !g_str_has_prefix (domain, "file://"))
tmp_domain = g_strdup_printf ("http://*%s/*", domain);
tmp_domain = g_strdup_printf ("http*://*%s/*", domain);
else
tmp_domain = domain;
@ -1448,6 +1468,12 @@ static gboolean
addons_skip_element (struct AddonElement* element,
gchar* uri)
{
if (midori_debug("addons:match"))
{
g_print("%s: %s on %s matched: %d\n", G_STRFUNC,
element->displayname, uri, addons_may_run (uri, &element->includes, &element->excludes));
}
if (!element->enabled || element->broken)
return TRUE;
if (element->includes || element->excludes)
@ -1866,6 +1892,8 @@ test_addons_simple_regexp (void)
{ "*", "^.*" },
{ "http://", "^http://" },
{ "https://", "^https://" },
{ "http*://", "^http://" },
{ "http*://", "^https://" },
{ "about:blank", "^about:blank" },
{ "file://", "^file://" },
{ "ftp://", "^ftp://" },

View file

@ -10,7 +10,8 @@
*/
namespace Apps {
const string EXEC_PREFIX = PACKAGE_NAME + " -a ";
const string APP_PREFIX = PACKAGE_NAME + " -a ";
const string PROFILE_PREFIX = PACKAGE_NAME + " -c ";
private class Launcher : GLib.Object, GLib.Initable {
internal GLib.File file;
@ -19,31 +20,145 @@ namespace Apps {
internal string exec;
internal string uri;
internal static async void create (GLib.File folder, string uri, string title) {
/* Strip LRE leading character and / */
string filename = title.delimit ("/", ' ') + ".desktop";
string exec = EXEC_PREFIX + uri;
string name = title;
// TODO: Midori.Paths.get_icon save to png
internal static string get_favicon_name_for_uri (string prefix, GLib.File folder, string uri, bool testing)
{
string icon_name = Midori.Stock.WEB_BROWSER;
string contents = """
[Desktop Entry]
Version=1.0
Type=Application
Name=%s
Exec=%s
TryExec=%s
Icon=%s
Categories=Network;
""".printf (name, exec, PACKAGE_NAME, icon_name);
var file = folder.get_child (filename);
if (testing == true)
return icon_name;
if (prefix != PROFILE_PREFIX)
{
try {
var stream = yield file.replace_async (null, false, GLib.FileCreateFlags.NONE);
yield stream.write_async (contents.data);
var pixbuf = Midori.Paths.get_icon (uri, null);
if (pixbuf == null)
throw new FileError.EXIST ("No favicon loaded");
string icon_filename = folder.get_child ("icon.png").get_path ();
pixbuf.save (icon_filename, "png", null, "compression", "7", null);
#if HAVE_WIN32
string doubleslash_icon = icon_filename.replace ("\\", "\\\\");
icon_name = doubleslash_icon;
#else
icon_name = icon_filename;
#endif
}
catch (Error error) {
// TODO GUI infobar
warning ("Failed to create new launcher: %s", error.message);
GLib.warning (_("Failed to fetch application icon in %s: %s"), folder.get_path (), error.message);
}
}
return icon_name;
}
internal static string prepare_desktop_file (string prefix, string name, string uri, string title, string icon_name)
{
string exec;
#if HAVE_WIN32
string doubleslash_uri = uri.replace ("\\", "\\\\");
string quoted_uri = GLib.Shell.quote (doubleslash_uri);
exec = prefix + quoted_uri;
#else
exec = prefix + uri;
#endif
var keyfile = new GLib.KeyFile ();
string entry = "Desktop Entry";
keyfile.set_string (entry, "Version", "1.0");
keyfile.set_string (entry, "Type", "Application");
keyfile.set_string (entry, "Name", name);
keyfile.set_string (entry, "Exec", exec);
keyfile.set_string (entry, "TryExec", PACKAGE_NAME);
keyfile.set_string (entry, "Icon", icon_name);
keyfile.set_string (entry, "Categories", "Network;");
/*
Using the sanitized URI as a class matches midori_web_app_new
So dock type launchers can distinguish different apps with the same executable
*/
if (exec.has_prefix (APP_PREFIX))
keyfile.set_string (entry, "StartupWMClass", uri.delimit (":.\\/", '_'));
return keyfile.to_data();
}
internal static File get_app_folder () {
var data_dir = File.new_for_path (Midori.Paths.get_user_data_dir ()).get_child (PACKAGE_NAME);
return data_dir.get_child ("apps");
}
internal static async File create_app (string uri, string title, Gtk.Widget? proxy) {
string checksum = Checksum.compute_for_string (ChecksumType.MD5, uri, -1);
var folder = get_app_folder ();
yield Launcher.create (APP_PREFIX, folder.get_child (checksum),
uri, title, proxy);
return folder.get_child (checksum);
}
internal static File get_profile_folder () {
var data_dir = File.new_for_path (Midori.Paths.get_user_data_dir ()).get_child (PACKAGE_NAME);
return data_dir.get_child ("profiles");
}
internal static async File create_profile (Gtk.Widget? proxy) {
string uuid = g_dbus_generate_guid ();
string config = Path.build_path (Path.DIR_SEPARATOR_S,
Midori.Paths.get_user_data_dir (), PACKAGE_NAME, "profiles", uuid);
var folder = get_profile_folder ();
yield Launcher.create (PROFILE_PREFIX, folder.get_child (uuid),
config, _("Midori (%s)").printf (uuid), proxy);
return folder.get_child (uuid);
}
internal static async void create (string prefix, GLib.File folder, string uri, string title, Gtk.Widget proxy) {
/* Strip LRE leading character and / */
string name = title.delimit ("/", ' ').strip();
string filename = Midori.Download.clean_filename (name);
string icon_name = Midori.Stock.WEB_BROWSER;
bool testing = false;
if (proxy == null)
testing = true;
var file = folder.get_child ("desc");
try {
folder.make_directory_with_parents (null);
} catch (IOError.EXISTS exist_error) {
/* It's no error if the folder already exists */
} catch (Error error) {
warning (_("Failed to create new launcher (%s): %s"), file.get_path (), error.message);
}
icon_name = get_favicon_name_for_uri (prefix, folder, uri, testing);
string desktop_file = prepare_desktop_file (prefix, name, uri, title, icon_name);
try {
var stream = yield file.replace_async (null, false, GLib.FileCreateFlags.NONE);
yield stream.write_async (desktop_file.data);
// Create a launcher/ menu
#if HAVE_WIN32
Midori.Sokoke.create_win32_desktop_lnk (prefix, filename, uri);
#else
var data_dir = File.new_for_path (Midori.Paths.get_user_data_dir ());
var desktop_dir = data_dir.get_child ("applications");
try {
desktop_dir.make_directory_with_parents (null);
} catch (IOError.EXISTS exist_error) {
/* It's no error if the folder already exists */
}
yield file.copy_async (desktop_dir.get_child (filename + ".desktop"),
GLib.FileCopyFlags.NONE);
#endif
if (proxy != null) {
var browser = proxy.get_toplevel () as Midori.Browser;
browser.send_notification (_("Launcher created"),
_("You can now run <b>%s</b> from your launcher or menu").printf (name));
}
}
catch (Error error) {
warning (_("Failed to create new launcher (%s): %s"), file.get_path (), error.message);
if (proxy != null) {
var browser = proxy.get_toplevel () as Midori.Browser;
browser.send_notification (_("Error creating launcher"),
_("Failed to create new launcher (%s): %s").printf (file.get_path (), error.message));
}
}
}
@ -52,18 +167,20 @@ namespace Apps {
}
bool init (GLib.Cancellable? cancellable) throws GLib.Error {
if (!file.get_basename ().has_suffix (".desktop"))
return false;
var keyfile = new GLib.KeyFile ();
keyfile.load_from_file (file.get_path (), GLib.KeyFileFlags.NONE);
try {
keyfile.load_from_file (file.get_child ("desc").get_path (), GLib.KeyFileFlags.NONE);
} catch (Error desc_error) {
throw new FileError.EXIST (_("No file \"desc\" found"));
}
exec = keyfile.get_string ("Desktop Entry", "Exec");
if (!exec.has_prefix (EXEC_PREFIX))
if (!exec.has_prefix (APP_PREFIX) && !exec.has_prefix (PROFILE_PREFIX))
return false;
name = keyfile.get_string ("Desktop Entry", "Name");
icon_name = keyfile.get_string ("Desktop Entry", "Icon");
uri = exec.replace (EXEC_PREFIX, "");
uri = exec.replace (APP_PREFIX, "").replace (PROFILE_PREFIX, "");
return true;
}
}
@ -73,6 +190,8 @@ namespace Apps {
Gtk.ListStore store = new Gtk.ListStore (1, typeof (Launcher));
Gtk.TreeView treeview;
Katze.Array array;
GLib.File app_folder;
GLib.File profile_folder;
public unowned string get_stock_id () {
return Midori.Stock.WEB_BROWSER;
@ -85,12 +204,93 @@ namespace Apps {
public Gtk.Widget get_toolbar () {
if (toolbar == null) {
toolbar = new Gtk.Toolbar ();
toolbar.set_icon_size (Gtk.IconSize.BUTTON);
#if !HAVE_WIN32
/* FIXME: Profiles are broken on win32 because of no multi instance support */
var profile = new Gtk.ToolButton.from_stock (Gtk.STOCK_ADD);
profile.label = _("New _Profile");
profile.tooltip_text = _("Creates a new, independent profile and a launcher");
profile.use_underline = true;
profile.is_important = true;
profile.show ();
profile.clicked.connect (() => {
Launcher.create_profile.begin (this);
});
toolbar.insert (profile, -1);
#endif
var app = new Gtk.ToolButton.from_stock (Gtk.STOCK_ADD);
app.label = _("New _App");
app.tooltip_text = _("Creates a new app for a specific site");
app.use_underline = true;
app.is_important = true;
app.show ();
app.clicked.connect (() => {
var view = (get_toplevel () as Midori.Browser).tab as Midori.View;
string checksum = Checksum.compute_for_string (ChecksumType.MD5, view.get_display_uri (), -1);
Launcher.create.begin (APP_PREFIX, app_folder.get_child (checksum),
view.get_display_uri (), view.get_display_title (), this);
});
toolbar.insert (app, -1);
}
return toolbar;
}
public Sidebar (Katze.Array array) {
void row_activated (Gtk.TreePath path, Gtk.TreeViewColumn column) {
Gtk.TreeIter iter;
if (store.get_iter (out iter, path)) {
Launcher launcher;
store.get (iter, 0, out launcher);
try {
GLib.Process.spawn_command_line_async (launcher.exec);
}
catch (Error error) {
var browser = get_toplevel () as Midori.Browser;
browser.send_notification (_("Error launching"), error.message);
}
}
}
bool button_released (Gdk.EventButton event) {
Gtk.TreePath? path;
Gtk.TreeViewColumn column;
if (event.button != 1)
return false;
if (treeview.get_path_at_pos ((int)event.x, (int)event.y, out path, out column, null, null)) {
if (path != null) {
if (column == treeview.get_column (2)) {
Gtk.TreeIter iter;
if (store.get_iter (out iter, path)) {
Launcher launcher;
store.get (iter, 0, out launcher);
try {
launcher.file.trash (null);
store.remove (iter);
string filename = Midori.Download.clean_filename (launcher.name);
#if HAVE_WIN32
string lnk_filename = Midori.Sokoke.get_win32_desktop_lnk_path_for_filename (filename);
if (Posix.access (lnk_filename, Posix.F_OK) == 0) {
var lnk_file = File.new_for_path (lnk_filename);
lnk_file.trash ();
}
#else
var data_dir = File.new_for_path (Midori.Paths.get_user_data_dir ());
data_dir.get_child ("applications").get_child (filename + ".desktop").trash ();
#endif
}
catch (Error error) {
GLib.critical ("Failed to remove launcher (%s): %s", launcher.file.get_path (), error.message);
}
return true;
}
}
}
}
return false;
}
public Sidebar (Katze.Array array, GLib.File app_folder, GLib.File profile_folder) {
Gtk.TreeViewColumn column;
treeview = new Gtk.TreeView.with_model (store);
@ -113,6 +313,14 @@ namespace Apps {
column.set_cell_data_func (renderer_text, on_render_text);
treeview.append_column (column);
column = new Gtk.TreeViewColumn ();
Gtk.CellRendererPixbuf renderer_button = new Gtk.CellRendererPixbuf ();
column.pack_start (renderer_button, false);
column.set_cell_data_func (renderer_button, on_render_button);
treeview.append_column (column);
treeview.row_activated.connect (row_activated);
treeview.button_release_event.connect (button_released);
treeview.show ();
pack_start (treeview, true, true, 0);
@ -121,6 +329,9 @@ namespace Apps {
array.remove_item.connect (launcher_removed);
foreach (GLib.Object item in array.get_items ())
launcher_added (item);
this.app_folder = app_folder;
this.profile_folder = profile_folder;
}
private int tree_sort_func (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b) {
@ -145,11 +356,18 @@ namespace Apps {
Launcher launcher;
model.get (iter, 0, out launcher);
if (launcher.icon_name != null)
try {
int icon_width = 48, icon_height = 48;
Gtk.icon_size_lookup_for_settings (get_settings (),
Gtk.IconSize.DIALOG, out icon_width, out icon_height);
var pixbuf = new Gdk.Pixbuf.from_file_at_size (launcher.icon_name, icon_width, icon_height);
renderer.set ("pixbuf", pixbuf);
}
catch (Error error) {
renderer.set ("icon-name", launcher.icon_name);
else
renderer.set ("stock-id", Gtk.STOCK_FILE);
renderer.set ("stock-size", Gtk.IconSize.BUTTON,
}
renderer.set ("stock-size", Gtk.IconSize.DIALOG,
"xpad", 4);
}
@ -163,12 +381,20 @@ namespace Apps {
launcher.name, launcher.uri),
"ellipsize", Pango.EllipsizeMode.END);
}
void on_render_button (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
renderer.set ("stock-id", Gtk.STOCK_DELETE,
"stock-size", Gtk.IconSize.MENU);
}
}
private class Manager : Midori.Extension {
internal Katze.Array array;
internal GLib.File app_folder;
internal GLib.FileMonitor? monitor;
internal GLib.File profile_folder;
internal GLib.List<GLib.FileMonitor> monitors;
internal GLib.List<Gtk.Widget> widgets;
void app_changed (GLib.File file, GLib.File? other, GLib.FileMonitorEvent event) {
@ -188,24 +414,21 @@ namespace Apps {
}
}
catch (Error error) {
warning ("Application changed: %s", error.message);
warning ("Application changed (%s): %s", file.get_path (), error.message);
}
}
async void populate_apps () {
var data_dir = File.new_for_path (Midori.Paths.get_user_data_dir ());
app_folder = data_dir.get_child ("applications");
async void populate_apps (File app_folder) {
try {
try {
app_folder.make_directory_with_parents (null);
}
catch (IOError folder_error) {
if (!(folder_error is IOError.EXISTS))
throw folder_error;
} catch (IOError.EXISTS exist_error) {
/* It's no error if the folder already exists */
}
monitor = app_folder.monitor_directory (0, null);
var monitor = app_folder.monitor_directory (0, null);
monitor.changed.connect (app_changed);
monitors.append (monitor);
var enumerator = yield app_folder.enumerate_children_async ("standard::name", 0);
while (true) {
@ -213,48 +436,55 @@ namespace Apps {
if (files == null)
break;
foreach (var info in files) {
var desktop_file = app_folder.get_child (info.get_name ());
var file = app_folder.get_child (info.get_name ());
try {
var launcher = new Launcher (desktop_file);
var launcher = new Launcher (file);
if (launcher.init ())
array.add_item (launcher);
}
catch (Error error) {
warning ("Failed to parse launcher: %s", error.message);
warning ("Failed to parse launcher (%s): %s", file.get_path (), error.message);
}
}
}
}
catch (Error io_error) {
monitor = null;
warning ("Failed to list .desktop files (%s): %s",
warning ("Failed to list apps (%s): %s",
app_folder.get_path (), io_error.message);
}
}
void tool_menu_populated (Midori.Browser browser, Gtk.Menu menu) {
var menuitem = new Gtk.MenuItem.with_mnemonic (_("Create _Launcher"));
menuitem.show ();
menu.append (menuitem);
menuitem.activate.connect (() => {
var view = browser.tab as Midori.View;
Launcher.create.begin (app_folder,
view.get_display_uri (), view.get_display_title ());
});
}
void browser_added (Midori.Browser browser) {
var viewable = new Sidebar (array);
var accels = new Gtk.AccelGroup ();
browser.add_accel_group (accels);
var action_group = browser.get_action_group ();
var action = new Gtk.Action ("CreateLauncher", _("Create _Launcher"),
_("Creates a new app for a specific site"), null);
action.activate.connect (() => {
var view = browser.tab as Midori.View;
Launcher.create_app.begin (view.get_display_uri (), view.get_display_title (), view);
});
action_group.add_action_with_accel (action, "<Ctrl><Shift>A");
action.set_accel_group (accels);
action.connect_accelerator ();
var viewable = new Sidebar (array, app_folder, profile_folder);
viewable.show ();
browser.panel.append_page (viewable);
browser.populate_tool_menu.connect (tool_menu_populated);
// TODO website context menu
widgets.append (viewable);
}
void activated (Midori.App app) {
array = new Katze.Array (typeof (Launcher));
populate_apps.begin ();
monitors = new GLib.List<GLib.FileMonitor> ();
app_folder = Launcher.get_app_folder ();
populate_apps.begin (app_folder);
/* FIXME: Profiles are broken on win32 because of no multi instance support */
profile_folder = Launcher.get_profile_folder ();
#if !HAVE_WIN32
populate_apps.begin (profile_folder);
#endif
widgets = new GLib.List<Gtk.Widget> ();
foreach (var browser in app.get_browsers ())
browser_added (browser);
@ -263,11 +493,19 @@ namespace Apps {
void deactivated () {
var app = get_app ();
if (monitor != null)
foreach (var monitor in monitors)
monitor.changed.disconnect (app_changed);
monitors = null;
app.add_browser.disconnect (browser_added);
foreach (var widget in widgets)
widget.destroy ();
foreach (var browser in app.get_browsers ()) {
var action_group = browser.get_action_group ();
var action = action_group.get_action ("CreateLauncher");
action_group.remove_action (action);
}
}
internal Manager () {
@ -286,3 +524,27 @@ public Midori.Extension extension_init () {
return new Apps.Manager ();
}
class ExtensionsAppsDesktop : Midori.Test.Job {
public static void test () { new ExtensionsAppsDesktop ().run_sync (); }
public override async void run (Cancellable cancellable) throws GLib.Error {
string uri = "http://example.com";
string checksum = Checksum.compute_for_string (ChecksumType.MD5, uri, -1);
var apps = Apps.Launcher.get_app_folder ().get_child (checksum);
Midori.Paths.remove_path (apps.get_path ());
var data_dir = File.new_for_path (Midori.Paths.get_user_data_dir ());
var desktop_dir = data_dir.get_child ("applications");
Midori.Paths.remove_path (desktop_dir.get_child ("Example.desktop").get_path ());
var folder = yield Apps.Launcher.create_app (uri, "Example", null);
var launcher = new Apps.Launcher (folder);
launcher.init ();
Katze.assert_str_equal (folder.get_path (), launcher.uri, uri);
yield Apps.Launcher.create_profile (null);
}
}
public void extension_test () {
Test.add_func ("/extensions/apps/desktop", ExtensionsAppsDesktop.test);
}

View file

@ -12,58 +12,91 @@
#include <midori/midori.h>
static GdkColor
view_get_bgcolor_for_hostname (MidoriView* view, gchar* hostname)
static void
get_foreground_color_for_GdkColor (GdkColor* color,
GdkColor* fgcolor)
{
GdkColor color;
GdkPixbuf* icon = midori_view_get_icon (view);
if (icon != NULL)
gfloat brightness, r, g, b;
r = color->red / 255;
g = color->green / 255;
b = color->blue / 255;
/* For math used see algorithms for converting from rgb to yuv */
brightness = 0.299 * r + 0.587 * g + 0.114 * b;
/* Ensure high contrast by enforcing black/ white text colour. */
/* Brigthness (range 0-255) equals value of y from YUV color space. */
if (brightness < 128)
gdk_color_parse ("white", fgcolor);
else
gdk_color_parse ("black", fgcolor);
}
static void
adjust_brightness (GdkColor* color)
{
guint dark_grey = 137 * 255;
guint adjustment = 78 * 255;
guint blue = 39 * 255;
guint readjust = 19 * 255;
if ((color->red < dark_grey)
&& (color->green < dark_grey)
&& (color->blue < dark_grey))
{
color->red += adjustment;
color->green += adjustment;
color->blue += adjustment;
}
if (color->red < blue)
color->red = readjust;
else
color->red -= readjust;
if (color->blue < blue)
color->blue = readjust;
else
color->blue -= readjust;
if (color->green < blue)
color->green = readjust;
else
color->green -= readjust;
}
static void
view_get_bgcolor_for_favicon (GdkPixbuf* icon,
GdkColor* color)
{
GdkPixbuf* newpix;
guchar* pixels;
newpix = gdk_pixbuf_scale_simple (icon, 1, 1, GDK_INTERP_BILINEAR);
pixels = gdk_pixbuf_get_pixels (newpix);
color.red = pixels[0] * 255;
color.green = pixels[1] * 255;
color.blue = pixels[2] * 255;
}
else
{
color->red = pixels[0] * 255;
color->green = pixels[1] * 255;
color->blue = pixels[2] * 255;
adjust_brightness (color);
}
static void
view_get_bgcolor_for_hostname (gchar* hostname,
GdkColor* color)
{
gchar* hash, *colorstr;
hash = g_compute_checksum_for_string (G_CHECKSUM_MD5, hostname, 1);
colorstr = g_strndup (hash, 6 + 1);
colorstr[0] = '#';
gdk_color_parse (colorstr, &color);
gdk_color_parse (colorstr, color);
g_free (hash);
g_free (colorstr);
}
if ((color.red < 35000)
&& (color.green < 35000)
&& (color.blue < 35000))
{
color.red += 20000;
color.green += 20000;
color.blue += 20000;
}
if (color.red < 10000)
color.red = 5000;
else
color.red -= 5000;
if (color.blue < 10000)
color.blue = 5000;
else
color.blue -= 5000;
if (color.green < 10000)
color.green = 5000;
else
color.green -= 5000;
return color;
adjust_brightness (color);
}
static void
@ -71,25 +104,28 @@ colorful_tabs_view_notify_uri_cb (MidoriView* view,
GParamSpec* pspec,
MidoriExtension* extension)
{
gchar* hostname;
GdkColor color;
GdkColor fgcolor;
const gchar* uri = midori_view_get_display_uri (view);
if (!*uri)
return;
if (!midori_uri_is_blank (midori_view_get_display_uri (view))
&& (hostname = midori_uri_parse_hostname (midori_view_get_display_uri (view), NULL))
&& midori_view_get_icon_uri (view) != NULL)
if (!midori_uri_is_blank (uri))
{
color = view_get_bgcolor_for_hostname (view, hostname);
g_free (hostname);
/* Ensure high contrast by enforcing black/ white text colour. */
if ((color.red < 41000)
&& (color.green < 41000)
&& (color.blue < 41000))
gdk_color_parse ("#fff", &fgcolor);
else
gdk_color_parse ("#000", &fgcolor);
gchar* hostname = midori_uri_parse_hostname (uri, NULL);
if (hostname)
{
GdkColor fgcolor, color;
GdkPixbuf* icon = midori_view_get_icon (view);
if (icon)
view_get_bgcolor_for_favicon (icon, &color);
else
view_get_bgcolor_for_hostname (hostname, &color);
get_foreground_color_for_GdkColor (&color, &fgcolor);
midori_view_set_colors (view, &fgcolor, &color);
g_free (hostname);
}
}
else
midori_view_set_colors (view, NULL, NULL);
@ -115,7 +151,6 @@ colorful_tabs_deactivate_cb (MidoriExtension* extension,
MidoriBrowser* browser)
{
GList* children;
GtkWidget* view;
MidoriApp* app = midori_extension_get_app (extension);
g_signal_handlers_disconnect_by_func (
@ -170,6 +205,51 @@ colorful_tabs_activate_cb (MidoriExtension* extension,
g_object_unref (browsers);
}
void test_colour_for_hostname (void)
{
GdkColor color;
GdkColor fgcolor;
typedef struct
{
const gchar* host;
const gchar* fgcolor;
const gchar* color;
} ColorItem;
static const ColorItem items[] = {
{ "www.last.fm", "#ffffffffffff", "#12ed7da312ed" },
{ "git.xfce.org", "#ffffffffffff", "#1c424c72e207" },
{ "elementaryos.org", "#000000000000", "#50dbac36b43e" },
{ "news.ycombinator.com", "#000000000000", "#a5cba6cc5278" },
{ "cgit.freedesktop.org", "#000000000000", "#95bb8db37ca2" },
{ "get.cm", "#ffffffffffff", "#1c424c72e207" },
};
guint i;
for (i = 0; i < G_N_ELEMENTS (items); i++)
{
view_get_bgcolor_for_hostname ((gchar*)items[i].host, &color);
get_foreground_color_for_GdkColor (&color, &fgcolor);
g_assert_cmpstr (items[i].color, ==, gdk_color_to_string (&color));
g_assert_cmpstr (items[i].fgcolor, ==, gdk_color_to_string (&fgcolor));
}
}
void
extension_test (void)
{
#ifndef HAVE_WEBKIT2
g_object_set_data (G_OBJECT (webkit_get_default_session ()),
"midori-session-initialized", (void*)1);
#endif
/* TODO: Add test which uses favicon codepath */
g_test_add_func ("/extensions/colorful_tabs/hostname_colour", test_colour_for_hostname);
}
MidoriExtension*
extension_init (void)
{

View file

@ -87,10 +87,6 @@ static void cm_create_toolbar(CookieManagerPage *cmp)
GtkToolItem *toolitem;
priv->toolbar = toolbar = gtk_toolbar_new();
gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH_HORIZ);
gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar), GTK_ICON_SIZE_BUTTON);
gtk_widget_show(toolbar);
toolitem = gtk_tool_button_new_from_stock(GTK_STOCK_DELETE);
gtk_tool_item_set_is_important(toolitem, TRUE);
g_signal_connect(toolitem, "clicked", G_CALLBACK(cm_button_delete_clicked_cb), cmp);
@ -167,7 +163,7 @@ static void cookie_manager_page_cookies_changed_cb(CookieManager *cm, CookieMana
g_object_unref(priv->filter);
/* if a filter is set, apply it again but ignore the place holder text */
if (!g_object_get_data (G_OBJECT (priv->filter_entry), "sokoke_has_default"))
if (!g_object_get_data (G_OBJECT (priv->filter_entry), "sokoke_showing_default"))
{
filter_text = gtk_entry_get_text(GTK_ENTRY(priv->filter_entry));
if (*filter_text != '\0')
@ -579,7 +575,7 @@ static void cm_button_delete_all_clicked_cb(GtkToolButton *button, CookieManager
if (toplevel != NULL)
gtk_window_set_icon_name(GTK_WINDOW(dialog), gtk_window_get_icon_name(GTK_WINDOW(toplevel)));
if (!g_object_get_data (G_OBJECT (priv->filter_entry), "sokoke_has_default"))
if (!g_object_get_data (G_OBJECT (priv->filter_entry), "sokoke_showing_default"))
{
filter_text = gtk_entry_get_text(GTK_ENTRY(priv->filter_entry));
if (*filter_text != '\0')
@ -664,17 +660,9 @@ static gchar *cm_get_cookie_description_text(SoupCookie *cookie)
if (cookie->expires != NULL)
{
time_t expiration_time = soup_date_to_time_t(cookie->expires);
#if GLIB_CHECK_VERSION (2, 26, 0)
GDateTime* date = g_date_time_new_from_unix_local(expiration_time);
expires = g_date_time_format(date, "%c");
g_date_time_unref(date);
#else
static gchar date_fmt[512];
const struct tm *tm = localtime(&expiration_time);
/* Some GCC versions falsely complain about "%c" */
strftime(date_fmt, sizeof(date_fmt), "%c", tm);
expires = g_strdup(date_fmt);
#endif
}
else
expires = g_strdup(_("At the end of the session"));
@ -820,7 +808,7 @@ static void cm_filter_entry_changed_cb(GtkEditable *editable, CookieManagerPage
if (priv->ignore_changed_filter)
return;
if (!g_object_get_data (G_OBJECT (editable), "sokoke_has_default"))
if (!g_object_get_data (G_OBJECT (editable), "sokoke_showing_default"))
text = gtk_entry_get_text(GTK_ENTRY(editable));
else
text = NULL;

View file

@ -12,6 +12,7 @@
#include "config.h"
#include <midori/midori.h>
#include "katze/katze.h"
#include <libsoup/soup-cookie-jar-sqlite.h>
#include "cookie-manager.h"
#include "cookie-manager-page.h"
@ -259,6 +260,7 @@ static void cookie_manager_finalize(GObject *object)
g_object_unref(priv->store);
g_free(priv->filter_text);
g_object_unref(priv->jar);
G_OBJECT_CLASS(cookie_manager_parent_class)->finalize(object);
}
@ -267,7 +269,6 @@ static void cookie_manager_finalize(GObject *object)
static void cookie_manager_init(CookieManager *self)
{
CookieManagerPrivate *priv;
SoupSession *session;
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
COOKIE_MANAGER_TYPE, CookieManagerPrivate);
@ -279,8 +280,15 @@ static void cookie_manager_init(CookieManager *self)
COOKIE_MANAGER_COL_NAME, GTK_SORT_ASCENDING);
/* setup soup */
session = webkit_get_default_session();
#ifdef HAVE_WEBKIT2
gchar *filename = midori_paths_get_config_filename_for_writing ("cookies.db");
priv->jar = soup_cookie_jar_sqlite_new (filename, FALSE);
g_free(filename);
#else
SoupSession *session = webkit_get_default_session();
priv->jar = SOUP_COOKIE_JAR(soup_session_get_feature(session, soup_cookie_jar_get_type()));
g_object_ref(priv->jar);
#endif
g_signal_connect(priv->jar, "changed", G_CALLBACK(cookie_manager_jar_changed_cb), self);
cookie_manager_refresh_store(self);

View file

@ -43,16 +43,17 @@ struct _CookiePermissionManagerPreferencesWindowPrivate
GtkListStore *listStore;
GtkWidget *list;
GtkTreeSelection *listSelection;
GtkWidget *editingCombo;
GtkWidget *deleteButton;
GtkWidget *deleteAllButton;
GtkWidget *askForUnknownPolicyCheckbox;
GtkWidget *unknownPolicyCombo;
GtkWidget *addDomainEntry;
GtkWidget *addDomainPolicyCombo;
GtkWidget *addDomainButton;
gint signalManagerChangedDatabaseID;
gint signalManagerAskForUnknownPolicyID;
gint signalAskForUnknownPolicyID;
gint signalManagerUnknownPolicyID;
gint signalUnknownPolicyID;
};
enum
@ -314,35 +315,130 @@ static void _cookie_permission_manager_preferences_window_manager_database_chang
return;
}
/* Ask-for-unknown-policy in manager changed or check-box changed */
static void _cookie_permission_manager_preferences_window_manager_ask_for_unknown_policy_changed(CookiePermissionManagerPreferencesWindow *self,
/* unknown-policy in manager changed or drop-down changed */
static void _cookie_permission_manager_preferences_window_manager_unknown_policy_changed(CookiePermissionManagerPreferencesWindow *self,
GParamSpec *inSpec,
gpointer inUserData)
{
CookiePermissionManagerPreferencesWindowPrivate *priv=self->priv;
CookiePermissionManager *manager=COOKIE_PERMISSION_MANAGER(inUserData);
gboolean doAsk;
CookiePermissionManagerPolicy policy;
/* Get new ask-for-unknown-policy value */
g_object_get(manager, "ask-for-unknown-policy", &doAsk, NULL);
/* Get new unknown-policy value */
g_object_get(manager, "unknown-policy", &policy, NULL);
/* Set toogle in widget (but block signal for toggle) */
g_signal_handler_block(priv->askForUnknownPolicyCheckbox, priv->signalAskForUnknownPolicyID);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->askForUnknownPolicyCheckbox), doAsk);
g_signal_handler_unblock(priv->askForUnknownPolicyCheckbox, priv->signalAskForUnknownPolicyID);
/* Set value in combobox (blocking signal to avoid loops) */
g_signal_handler_block(priv->unknownPolicyCombo, priv->signalUnknownPolicyID);
gtk_combo_box_set_active(GTK_COMBO_BOX(priv->unknownPolicyCombo), policy);
g_signal_handler_unblock(priv->unknownPolicyCombo, priv->signalUnknownPolicyID);
}
static void _cookie_permission_manager_preferences_window_ask_for_unknown_policy_changed(CookiePermissionManagerPreferencesWindow *self,
static void _cookie_permission_manager_preferences_window_unknown_policy_changed(CookiePermissionManagerPreferencesWindow *self,
gpointer *inUserData)
{
CookiePermissionManagerPreferencesWindowPrivate *priv=self->priv;
gboolean doAsk;
CookiePermissionManagerPolicy policy;
GtkTreeIter policyIter;
if(!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(priv->unknownPolicyCombo), &policyIter))
return;
gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(priv->unknownPolicyCombo)),
&policyIter,
0, &policy,
-1);
/* Get toogle state of widget (but block signal for manager) and set in manager */
g_signal_handler_block(priv->manager, priv->signalManagerAskForUnknownPolicyID);
doAsk=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->askForUnknownPolicyCheckbox));
g_object_set(priv->manager, "ask-for-unknown-policy", doAsk, NULL);
g_signal_handler_unblock(priv->manager, priv->signalManagerAskForUnknownPolicyID);
g_signal_handler_block(priv->manager, priv->signalManagerUnknownPolicyID);
g_object_set(priv->manager, "unknown-policy", policy, NULL);
g_signal_handler_unblock(priv->manager, priv->signalManagerUnknownPolicyID);
}
static void _cookie_permission_manager_preferences_on_policy_editing_started(CookiePermissionManagerPreferencesWindow *self,
GtkCellEditable *editable,
gchar *path,
gpointer *inUserData)
{
CookiePermissionManagerPreferencesWindowPrivate *priv=self->priv;
priv->editingCombo=NULL;
if(!GTK_IS_COMBO_BOX(editable)) return;
priv->editingCombo=GTK_WIDGET(editable);
}
static void _cookie_permission_manager_preferences_on_policy_editing_canceled(CookiePermissionManagerPreferencesWindow *self,
gpointer *inUserData)
{
CookiePermissionManagerPreferencesWindowPrivate *priv=self->priv;
priv->editingCombo=NULL;
}
static void _cookie_permission_manager_preferences_on_policy_edited(CookiePermissionManagerPreferencesWindow *self,
gchar *path,
gchar *newText,
gpointer *inUserData)
{
CookiePermissionManagerPreferencesWindowPrivate *priv=self->priv;
gchar *domain;
GtkTreeIter iter;
GtkTreeIter policyIter;
g_return_if_fail(priv->database);
if (priv->editingCombo == NULL) return;
gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(priv->listStore), &iter, path);
gtk_tree_model_get(GTK_TREE_MODEL(priv->listStore),
&iter,
DOMAIN_COLUMN, &domain,
-1);
/* Get policy from combo box */
if(gtk_combo_box_get_active_iter(GTK_COMBO_BOX(priv->editingCombo), &policyIter))
{
gchar *sql;
gchar *error=NULL;
gint success;
gint policy;
gchar *policyName;
/* Get policy value to set for domain */
gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(priv->editingCombo)),
&policyIter,
0, &policy,
1, &policyName,
-1);
g_return_if_fail(g_strcmp0(policyName, newText)==0);
/* Add domain name and the selected policy to database */
sql=sqlite3_mprintf("UPDATE policies SET value = %d WHERE domain = '%q';",
policy,
domain);
success=sqlite3_exec(priv->database, sql, NULL, NULL, &error);
/* Show error message if any */
if(success==SQLITE_OK)
{
gtk_list_store_set(priv->listStore,
&iter,
POLICY_COLUMN, newText,
-1);
}
else g_warning(_("SQL fails: %s"), error);
if(error) sqlite3_free(error);
/* Free allocated resources */
sqlite3_free(sql);
}
priv->editingCombo=NULL;
}
/* Selection in list changed */
@ -500,8 +596,8 @@ static void cookie_permission_manager_preferences_window_finalize(GObject *inObj
if(priv->signalManagerChangedDatabaseID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedDatabaseID);
priv->signalManagerChangedDatabaseID=0;
if(priv->signalManagerAskForUnknownPolicyID) g_signal_handler_disconnect(priv->manager, priv->signalManagerAskForUnknownPolicyID);
priv->signalManagerAskForUnknownPolicyID=0;
if(priv->signalManagerUnknownPolicyID) g_signal_handler_disconnect(priv->manager, priv->signalManagerUnknownPolicyID);
priv->signalManagerUnknownPolicyID=0;
g_object_unref(priv->manager);
priv->manager=NULL;
@ -531,8 +627,8 @@ static void cookie_permission_manager_preferences_window_set_property(GObject *i
if(priv->signalManagerChangedDatabaseID) g_signal_handler_disconnect(priv->manager, priv->signalManagerChangedDatabaseID);
priv->signalManagerChangedDatabaseID=0;
if(priv->signalManagerAskForUnknownPolicyID) g_signal_handler_disconnect(priv->manager, priv->signalManagerAskForUnknownPolicyID);
priv->signalManagerAskForUnknownPolicyID=0;
if(priv->signalManagerUnknownPolicyID) g_signal_handler_disconnect(priv->manager, priv->signalManagerUnknownPolicyID);
priv->signalManagerUnknownPolicyID=0;
g_object_unref(priv->manager);
priv->manager=NULL;
@ -553,12 +649,12 @@ static void cookie_permission_manager_preferences_window_set_property(GObject *i
self);
_cookie_permission_manager_preferences_window_manager_database_changed(self, NULL, priv->manager);
priv->signalManagerAskForUnknownPolicyID=
priv->signalManagerUnknownPolicyID=
g_signal_connect_swapped(priv->manager,
"notify::ask-for-unknown-policy",
G_CALLBACK(_cookie_permission_manager_preferences_window_manager_ask_for_unknown_policy_changed),
"notify::unknown-policy",
G_CALLBACK(_cookie_permission_manager_preferences_window_manager_unknown_policy_changed),
self);
_cookie_permission_manager_preferences_window_manager_ask_for_unknown_policy_changed(self, NULL, priv->manager);
_cookie_permission_manager_preferences_window_manager_unknown_policy_changed(self, NULL, priv->manager);
}
break;
@ -723,7 +819,7 @@ static void cookie_permission_manager_preferences_window_init(CookiePermissionMa
gtk_container_add(GTK_CONTAINER(hbox), priv->addDomainButton);
g_signal_connect_swapped(priv->addDomainButton, "clicked", G_CALLBACK(_cookie_permission_manager_preferences_on_add_domain_clicked), self);
gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 5);
/* Set up cookie domain list */
priv->list=gtk_tree_view_new_with_model(GTK_TREE_MODEL(priv->listStore));
@ -744,7 +840,11 @@ static void cookie_permission_manager_preferences_window_init(CookiePermissionMa
gtk_tree_view_column_set_sort_column_id(column, DOMAIN_COLUMN);
gtk_tree_view_append_column(GTK_TREE_VIEW(priv->list), column);
renderer=gtk_cell_renderer_text_new();
renderer=gtk_cell_renderer_combo_new();
g_object_set(G_OBJECT(renderer), "model", list, "text-column", 1, "has-entry", false, "editable", true, NULL);
g_signal_connect_swapped(renderer, "editing-started", G_CALLBACK(_cookie_permission_manager_preferences_on_policy_editing_started), self);
g_signal_connect_swapped(renderer, "editing-canceled", G_CALLBACK(_cookie_permission_manager_preferences_on_policy_editing_canceled), self);
g_signal_connect_swapped(renderer, "edited", G_CALLBACK(_cookie_permission_manager_preferences_on_policy_edited), self);
column=gtk_tree_view_column_new_with_attributes(_("Policy"),
renderer,
"text", POLICY_COLUMN,
@ -780,18 +880,44 @@ static void cookie_permission_manager_preferences_window_init(CookiePermissionMa
gtk_container_add(GTK_CONTAINER(hbox), priv->deleteAllButton);
g_signal_connect_swapped(priv->deleteAllButton, "clicked", G_CALLBACK(_cookie_permission_manager_preferences_on_delete_all), self);
gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 5);
/* Add "ask-for-unknown-policy" checkbox */
priv->askForUnknownPolicyCheckbox=gtk_check_button_new_with_mnemonic(_("A_sk for policy if unknown for a domain"));
priv->signalAskForUnknownPolicyID=g_signal_connect_swapped(priv->askForUnknownPolicyCheckbox,
"toggled",
G_CALLBACK(_cookie_permission_manager_preferences_window_ask_for_unknown_policy_changed),
/* Add "unknown-policy" combo */
#ifdef HAVE_GTK3
hbox=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_box_set_homogeneous(GTK_BOX(hbox), FALSE);
#else
hbox=gtk_hbox_new(FALSE, 0);
#endif
widget=gtk_label_new(_("Policy for cookies from domains not in the list: "));
gtk_container_add(GTK_CONTAINER(hbox), widget);
list=gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING);
gtk_list_store_append(list, &listIter);
gtk_list_store_set(list, &listIter, 0, COOKIE_PERMISSION_MANAGER_POLICY_UNDETERMINED, 1, _("Ask for a decision"), -1);
gtk_list_store_append(list, &listIter);
gtk_list_store_set(list, &listIter, 0, COOKIE_PERMISSION_MANAGER_POLICY_ACCEPT, 1, _("Accept"), -1);
gtk_list_store_append(list, &listIter);
gtk_list_store_set(list, &listIter, 0, COOKIE_PERMISSION_MANAGER_POLICY_ACCEPT_FOR_SESSION, 1, _("Accept for session"), -1);
gtk_list_store_append(list, &listIter);
gtk_list_store_set(list, &listIter, 0, COOKIE_PERMISSION_MANAGER_POLICY_BLOCK, 1, _("Block"), -1);
priv->unknownPolicyCombo=gtk_combo_box_new_with_model(GTK_TREE_MODEL(list));
gtk_combo_box_set_active(GTK_COMBO_BOX(priv->unknownPolicyCombo), 0);
gtk_container_add(GTK_CONTAINER(hbox), priv->unknownPolicyCombo);
renderer=gtk_cell_renderer_text_new();
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(priv->unknownPolicyCombo), renderer, TRUE);
gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(priv->unknownPolicyCombo), renderer, "text", 1);
priv->signalUnknownPolicyID=g_signal_connect_swapped(priv->unknownPolicyCombo,
"changed",
G_CALLBACK(_cookie_permission_manager_preferences_window_unknown_policy_changed),
self);
gtk_box_pack_start(GTK_BOX(vbox), priv->askForUnknownPolicyCheckbox, TRUE, TRUE, 5);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 5);
/* Finalize setup of content area */
gtk_container_add(GTK_CONTAINER(priv->contentArea), vbox);
gtk_box_pack_start(GTK_BOX(priv->contentArea), vbox, TRUE, TRUE, 0);
}
/* Implementation: Public API */

View file

@ -31,7 +31,7 @@ enum
PROP_DATABASE,
PROP_DATABASE_FILENAME,
PROP_ASK_FOR_UNKNOWN_POLICY,
PROP_UNKNOWN_POLICY,
PROP_LAST
};
@ -49,7 +49,7 @@ struct _CookiePermissionManagerPrivate
MidoriApp *application;
sqlite3 *database;
gchar *databaseFilename;
gboolean askForUnknownPolicy;
CookiePermissionManagerPolicy unknownPolicy;
/* Cookie jar related */
SoupSession *session;
@ -224,8 +224,9 @@ static void _cookie_permission_manager_open_database(CookiePermissionManager *se
uri=soup_uri_new(NULL);
soup_uri_set_host(uri, domain);
soup_uri_set_path(uri, "/");
cookies=soup_cookie_jar_get_cookie_list(priv->cookieJar, uri, TRUE);
for(cookie=cookies; cookie; cookie->next)
for(cookie=cookies; cookie; cookie=cookie->next)
{
soup_cookie_jar_delete_cookie(priv->cookieJar, (SoupCookie*)cookie->data);
}
@ -294,24 +295,23 @@ static gint _cookie_permission_manager_get_policy(CookiePermissionManager *self,
sqlite3_finalize(statement);
/* Check if policy is undetermined. If it is then check if this policy was set by user.
* If it was not set by user check if we should ask user for his decision
* If it was not set by user, check what to do.
*/
if(!priv->askForUnknownPolicy && !foundPolicy)
if(!foundPolicy)
{
switch(soup_cookie_jar_get_accept_policy(priv->cookieJar))
/* A SoupCookieJar that doesn't want to accept any cookies should override the user's
* choice, in case of e.g. private mode, to err on the side of caution. */
SoupCookieJarAcceptPolicy soup_policy=soup_cookie_jar_get_accept_policy(priv->cookieJar);
if(soup_policy==SOUP_COOKIE_JAR_ACCEPT_ALWAYS || soup_policy==SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
{
case SOUP_COOKIE_JAR_ACCEPT_ALWAYS:
case SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY:
policy=COOKIE_PERMISSION_MANAGER_POLICY_ACCEPT;
break;
case SOUP_COOKIE_JAR_ACCEPT_NEVER:
policy=COOKIE_PERMISSION_MANAGER_POLICY_BLOCK;
break;
default:
policy=priv->unknownPolicy;
}
else
{
if(soup_policy!=SOUP_COOKIE_JAR_ACCEPT_NEVER)
g_critical(_("Could not determine global cookie policy to set for domain: %s"), domain);
break;
policy=COOKIE_PERMISSION_MANAGER_POLICY_BLOCK;
}
}
@ -458,10 +458,11 @@ static gint _cookie_permission_manager_ask_for_policy(CookiePermissionManager *s
gint numberDomains, numberCookies;
GSList *sortedCookies, *cookies;
WebKitWebView *webkitView;
CookiePermissionManagerModalInfobar modalInfo;
CookiePermissionManagerModalInfobar *modalInfo;
/* Get webkit view of midori view */
webkitView=WEBKIT_WEB_VIEW(midori_view_get_web_view(inView));
modalInfo=g_new0(CookiePermissionManagerModalInfobar, 1);
/* Create a copy of cookies and sort them */
sortedCookies=_cookie_permission_manager_get_number_domains_and_cookies(self,
@ -535,16 +536,12 @@ static gint _cookie_permission_manager_ask_for_policy(CookiePermissionManager *s
* but I don't want to create an GObject just for a simple struct. So set object
* data by our own
*/
g_object_set_data(G_OBJECT(infobar), "cookie-permission-manager-infobar-data", &modalInfo);
g_object_set_data_full(G_OBJECT(infobar), "cookie-permission-manager-infobar-data", modalInfo, (GDestroyNotify)g_free);
/* FIXME: Find a way to add "details" widget */
#ifndef NO_INFOBAR_DETAILS
/* Get content area of infobar */
#if HAVE_GTK_INFO_BAR
contentArea=gtk_info_bar_get_content_area(GTK_INFO_BAR(infobar));
#else
contentArea=infobar;
#endif
/* Create list and set up columns of list */
list=gtk_tree_view_new_with_model(GTK_TREE_MODEL(listStore));
@ -614,19 +611,19 @@ static gint _cookie_permission_manager_ask_for_policy(CookiePermissionManager *s
/* Connect signals to quit main loop */
g_signal_connect(webkitView, "navigation-policy-decision-requested", G_CALLBACK(_cookie_permission_manager_on_infobar_webview_navigate), infobar);
g_signal_connect(infobar, "destroy", G_CALLBACK(_cookie_permission_manager_on_infobar_destroy), &modalInfo);
g_signal_connect(infobar, "destroy", G_CALLBACK(_cookie_permission_manager_on_infobar_destroy), modalInfo);
/* Let info bar be modal and set response to default */
modalInfo.response=COOKIE_PERMISSION_MANAGER_POLICY_UNDETERMINED;
modalInfo.mainLoop=g_main_loop_new(NULL, FALSE);
modalInfo->response=COOKIE_PERMISSION_MANAGER_POLICY_UNDETERMINED;
modalInfo->mainLoop=g_main_loop_new(NULL, FALSE);
GDK_THREADS_LEAVE();
g_main_loop_run(modalInfo.mainLoop);
g_main_loop_run(modalInfo->mainLoop);
GDK_THREADS_ENTER();
g_main_loop_unref(modalInfo.mainLoop);
g_main_loop_unref(modalInfo->mainLoop);
modalInfo.mainLoop=NULL;
modalInfo->mainLoop=NULL;
/* Disconnect signal handler to webkit's web view */
g_signal_handlers_disconnect_by_func(webkitView, G_CALLBACK(_cookie_permission_manager_on_infobar_webview_navigate), infobar);
@ -636,7 +633,7 @@ static gint _cookie_permission_manager_ask_for_policy(CookiePermissionManager *s
* updates of database for the same domain. This sorted list is a copy
* to avoid a reorder of cookies
*/
if(modalInfo.response!=COOKIE_PERMISSION_MANAGER_POLICY_UNDETERMINED)
if(modalInfo->response!=COOKIE_PERMISSION_MANAGER_POLICY_UNDETERMINED)
{
const gchar *lastDomain=NULL;
@ -657,7 +654,7 @@ static gint _cookie_permission_manager_ask_for_policy(CookiePermissionManager *s
sql=sqlite3_mprintf("INSERT OR REPLACE INTO policies (domain, value) VALUES ('%q', %d);",
cookieDomain,
modalInfo.response);
modalInfo->response);
success=sqlite3_exec(priv->database, sql, NULL, NULL, &error);
if(success!=SQLITE_OK) g_warning(_("SQL fails: %s"), error);
if(error) sqlite3_free(error);
@ -672,8 +669,8 @@ static gint _cookie_permission_manager_ask_for_policy(CookiePermissionManager *s
g_slist_free(sortedCookies);
/* Return response */
return(modalInfo.response==COOKIE_PERMISSION_MANAGER_POLICY_UNDETERMINED ?
COOKIE_PERMISSION_MANAGER_POLICY_BLOCK : modalInfo.response);
return(modalInfo->response==COOKIE_PERMISSION_MANAGER_POLICY_UNDETERMINED ?
COOKIE_PERMISSION_MANAGER_POLICY_BLOCK : modalInfo->response);
}
/* A cookie was changed outside a request (e.g. Javascript) */
@ -945,8 +942,8 @@ static void cookie_permission_manager_set_property(GObject *inObject,
_cookie_permission_manager_on_application_changed(self);
break;
case PROP_ASK_FOR_UNKNOWN_POLICY:
cookie_permission_manager_set_ask_for_unknown_policy(self, g_value_get_boolean(inValue));
case PROP_UNKNOWN_POLICY:
cookie_permission_manager_set_unknown_policy(self, g_value_get_int(inValue));
break;
default:
@ -980,8 +977,8 @@ static void cookie_permission_manager_get_property(GObject *inObject,
g_value_set_string(outValue, self->priv->databaseFilename);
break;
case PROP_ASK_FOR_UNKNOWN_POLICY:
g_value_set_boolean(outValue, self->priv->askForUnknownPolicy);
case PROP_UNKNOWN_POLICY:
g_value_set_int(outValue, self->priv->unknownPolicy);
break;
default:
@ -1033,12 +1030,14 @@ static void cookie_permission_manager_class_init(CookiePermissionManagerClass *k
NULL,
G_PARAM_READABLE);
CookiePermissionManagerProperties[PROP_ASK_FOR_UNKNOWN_POLICY]=
g_param_spec_boolean("ask-for-unknown-policy",
_("Ask for unknown policy"),
_("If true this extension ask for policy for every unknown domain."
"If false this extension uses the global cookie policy set in Midori settings."),
TRUE,
CookiePermissionManagerProperties[PROP_UNKNOWN_POLICY]=
g_param_spec_int("unknown-policy",
_("Unknown domain policy"),
_("The policy to use for domains not individually configured."
" This only acts to further restrict the global cookie policy set in Midori settings."),
COOKIE_PERMISSION_MANAGER_POLICY_UNDETERMINED,
COOKIE_PERMISSION_MANAGER_POLICY_BLOCK,
COOKIE_PERMISSION_MANAGER_POLICY_UNDETERMINED,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
g_object_class_install_properties(gobjectClass, PROP_LAST, CookiePermissionManagerProperties);
@ -1056,7 +1055,7 @@ static void cookie_permission_manager_init(CookiePermissionManager *self)
/* Set up default values */
priv->database=NULL;
priv->databaseFilename=NULL;
priv->askForUnknownPolicy=TRUE;
priv->unknownPolicy=COOKIE_PERMISSION_MANAGER_POLICY_UNDETERMINED;
/* Hijack session's cookie jar to handle cookies requests on our own in HTTP streams
* but remember old handlers to restore them on deactivation
@ -1082,22 +1081,22 @@ CookiePermissionManager* cookie_permission_manager_new(MidoriExtension *inExtens
}
/* Get/set policy to ask for policy if unknown for a domain */
gboolean cookie_permission_manager_get_ask_for_unknown_policy(CookiePermissionManager *self)
CookiePermissionManagerPolicy cookie_permission_manager_get_unknown_policy(CookiePermissionManager *self)
{
g_return_val_if_fail(IS_COOKIE_PERMISSION_MANAGER(self), FALSE);
g_return_val_if_fail(IS_COOKIE_PERMISSION_MANAGER(self), COOKIE_PERMISSION_MANAGER_POLICY_UNDETERMINED);
return(self->priv->askForUnknownPolicy);
return(self->priv->unknownPolicy);
}
void cookie_permission_manager_set_ask_for_unknown_policy(CookiePermissionManager *self, gboolean inDoAsk)
void cookie_permission_manager_set_unknown_policy(CookiePermissionManager *self, CookiePermissionManagerPolicy inPolicy)
{
g_return_if_fail(IS_COOKIE_PERMISSION_MANAGER(self));
if(inDoAsk!=self->priv->askForUnknownPolicy)
if(inPolicy!=self->priv->unknownPolicy)
{
self->priv->askForUnknownPolicy=inDoAsk;
midori_extension_set_boolean(self->priv->extension, "ask-for-unknown-policy", inDoAsk);
g_object_notify_by_pspec(G_OBJECT(self), CookiePermissionManagerProperties[PROP_ASK_FOR_UNKNOWN_POLICY]);
self->priv->unknownPolicy=inPolicy;
midori_extension_set_integer(self->priv->extension, "unknown-policy", inPolicy);
g_object_notify_by_pspec(G_OBJECT(self), CookiePermissionManagerProperties[PROP_UNKNOWN_POLICY]);
}
}

View file

@ -60,8 +60,8 @@ GType cookie_permission_manager_get_type(void);
CookiePermissionManager* cookie_permission_manager_new(MidoriExtension *inExtension, MidoriApp *inApp);
gboolean cookie_permission_manager_get_ask_for_unknown_policy(CookiePermissionManager *self);
void cookie_permission_manager_set_ask_for_unknown_policy(CookiePermissionManager *self, gboolean inDoAsk);
CookiePermissionManagerPolicy cookie_permission_manager_get_unknown_policy(CookiePermissionManager *self);
void cookie_permission_manager_set_unknown_policy(CookiePermissionManager *self, CookiePermissionManagerPolicy inPolicy);
/* Enumeration */
GType cookie_permission_manager_policy_get_type(void) G_GNUC_CONST;

View file

@ -21,7 +21,7 @@ static void _cpm_on_activate(MidoriExtension *inExtension, MidoriApp *inApp, gpo
g_return_if_fail(cpm==NULL);
cpm=cookie_permission_manager_new(inExtension, inApp);
g_object_set(cpm, "ask-for-unknown-policy", midori_extension_get_boolean(inExtension, "ask-for-unknown-policy"), NULL);
g_object_set(cpm, "unknown-policy", midori_extension_get_integer(inExtension, "unknown-policy"), NULL);
}
/* This extension was deactivated */
@ -65,7 +65,7 @@ MidoriExtension *extension_init(void)
"authors", "Stephan Haller <nomad@froevel.de>",
NULL);
midori_extension_install_boolean(extension, "ask-for-unknown-policy", TRUE);
midori_extension_install_integer(extension, "unknown-policy", COOKIE_PERMISSION_MANAGER_POLICY_UNDETERMINED);
midori_extension_install_boolean(extension, "show-details-when-ask", FALSE);
g_signal_connect(extension, "activate", G_CALLBACK(_cpm_on_activate), NULL);

View file

@ -9,171 +9,19 @@
See the file COPYING for the full license text.
*/
using Gtk;
using Katze;
using Midori;
namespace DelayedLoad {
private class PreferencesDialog : Gtk.Dialog {
protected Manager dl_manager;
protected Scale slider;
public PreferencesDialog (Manager manager) {
this.dl_manager = manager;
this.title = _("Preferences for %s").printf ( _("Delayed load"));
if (this.get_class ().find_property ("has-separator") != null)
this.set ("has-separator", false);
this.border_width = 5;
this.set_modal (true);
this.set_default_size (350, 100);
this.create_widgets ();
this.response.connect (response_cb);
}
private void response_cb (Gtk.Dialog source, int response_id) {
switch (response_id) {
case ResponseType.APPLY:
this.dl_manager.set_integer ("delay", (int) (this.slider.get_value () * 1000));
this.dl_manager.preferences_changed ();
this.destroy ();
break;
case ResponseType.CANCEL:
this.destroy ();
break;
}
}
private void create_widgets () {
Label text = new Label (_("Delay in seconds until loading the page:"));
#if HAVE_GTK3
this.slider = new Scale.with_range (Orientation.HORIZONTAL, 0, 15, 0.1);
#else
this.slider = new HScale.with_range (0, 15, 0.1);
#endif
int delay = this.dl_manager.get_integer ("delay");
if (delay > 0)
this.slider.set_value ((float)delay / 1000);
#if HAVE_GTK3
Gtk.Box vbox = get_content_area () as Gtk.Box;
vbox.pack_start (text, false, false, 0);
vbox.pack_start (this.slider, false, true, 0);
#else
this.vbox.pack_start (text, false, false, 0);
this.vbox.pack_start (this.slider, false, true, 0);
#endif
this.add_button (Gtk.STOCK_CANCEL, ResponseType.CANCEL);
this.add_button (Gtk.STOCK_APPLY, ResponseType.APPLY);
this.show_all ();
}
}
private class TabShaker : GLib.Object {
public unowned Midori.Browser browser;
public GLib.PtrArray tasks;
public bool reload_tab () {
if (tasks.len == 1) {
Midori.View? view = browser.tab as Midori.View;
Midori.View scheduled_view = tasks.index (0) as Midori.View;
if (scheduled_view == view) {
Katze.Item item = view.get_proxy_item ();
item.ref();
int64 delay = item.get_meta_integer ("delay");
if (delay == Midori.Delay.PENDING_UNDELAY) {
view.reload (true);
}
}
}
tasks.remove_index (0);
return false;
}
public TabShaker (Midori.Browser browser) {
this.browser = browser;
}
construct {
this.tasks = new GLib.PtrArray ();
}
}
private class Manager : Midori.Extension {
private int timeout = 0;
private bool initialized = false;
private HashTable<Midori.Browser, TabShaker> tasks;
public signal void preferences_changed ();
private void preferences_changed_cb () {
this.timeout = get_integer ("delay");
}
private void show_preferences () {
PreferencesDialog dialog = new PreferencesDialog (this);
dialog.show ();
}
private void schedule_reload (Midori.Browser browser, Midori.View view) {
if (this.timeout == 0)
view.reload (true);
else {
unowned TabShaker shaker = tasks.get (browser);
if (shaker != null) {
shaker.tasks.add (view);
Midori.Timeout.add (this.timeout, shaker.reload_tab);
}
}
}
private void tab_changed (Midori.View? old_view, Midori.View? new_view) {
if (new_view != null) {
Midori.App app = get_app ();
Midori.Browser browser = app.browser;
Katze.Item item = new_view.get_proxy_item ();
item.ref();
unowned Katze.Item item = new_view.get_proxy_item ();
int64 delay = item.get_meta_integer ("delay");
if (delay == Midori.Delay.PENDING_UNDELAY && new_view.progress < 1.0 && this.initialized) {
this.schedule_reload (browser, new_view);
if (delay == Midori.Delay.PENDING_UNDELAY && new_view.progress < 1.0) {
new_view.reload (true);
}
}
}
private bool reload_first_tab () {
Midori.App app = get_app ();
Midori.Browser? browser = app.browser;
Midori.View? view = browser.tab as Midori.View;
if (view != null) {
this.initialized = true;
Katze.Item item = view.get_proxy_item ();
item.ref();
int64 delay = item.get_meta_integer ("delay");
if (delay != Midori.Delay.DELAYED) {
if (view.load_status == Midori.LoadStatus.FINISHED) {
if (this.timeout != 0)
this.tasks.set (browser, new TabShaker (browser));
if (view.progress < 1.0)
this.schedule_reload (browser, view);
return false;
}
}
}
return true;
}
private void browser_added (Midori.Browser browser) {
browser.switch_tab.connect_after (this.tab_changed);
}
@ -183,17 +31,6 @@ namespace DelayedLoad {
}
public void activated (Midori.App app) {
/* FIXME: override behavior without changing the preference */
app.settings.load_on_startup = MidoriStartup.DELAYED_PAGES;
this.preferences_changed ();
Midori.Browser? focused_browser = app.browser;
if (focused_browser == null)
Midori.Timeout.add (50, this.reload_first_tab);
else
this.initialized = true;
foreach (Midori.Browser browser in app.get_browsers ()) {
browser_added (browser);
}
@ -211,17 +48,11 @@ namespace DelayedLoad {
internal Manager () {
GLib.Object (name: _("Delayed load"),
description: _("Delay page load until you actually use the tab."),
version: "0.1",
version: "0.2",
authors: "André Stösel <andre@stoesel.de>");
install_integer ("delay", 0);
activate.connect (this.activated);
deactivate.connect (this.deactivated);
open_preferences.connect (show_preferences);
preferences_changed.connect (preferences_changed_cb);
this.tasks = new HashTable<Midori.Browser, TabShaker> (GLib.direct_hash, GLib.direct_equal);
}
}
}

270
extensions/devpet.vala Normal file
View file

@ -0,0 +1,270 @@
/*
Copyright (C) 2013 André Stösel <andre@stoesel.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
Gtk.IconTheme theme;
namespace DevPet {
enum TreeCells {
MESSAGE,
BACKTRACE,
STOCK,
COUNT
}
private class DataWindow : Gtk.Window {
public string message {get; construct; }
public string backtrace {get; construct; }
private void create_content () {
this.title = this.message;
this.set_default_size (500, 500);
Gtk.VBox vbox = new Gtk.VBox (false, 1);
this.add (vbox);
Gtk.TextBuffer message_buffer = new Gtk.TextBuffer (null);
message_buffer.set_text (this.message);
Gtk.TextView message_text_view = new Gtk.TextView.with_buffer (message_buffer);
message_text_view.editable = false;
Gtk.TextBuffer backtrace_buffer = new Gtk.TextBuffer (null);
backtrace_buffer.set_text (this.backtrace);
Gtk.TextView backtrace_text_view = new Gtk.TextView.with_buffer (backtrace_buffer);
backtrace_text_view.editable = false;
Gtk.ScrolledWindow message_scroll = new Gtk.ScrolledWindow (null, null);
message_scroll.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
message_scroll.add (message_text_view);
Gtk.ScrolledWindow backtrace_scroll = new Gtk.ScrolledWindow (null, null);
backtrace_scroll.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
backtrace_scroll.add (backtrace_text_view);
vbox.pack_start (message_scroll, false, false, 0);
vbox.pack_end (backtrace_scroll, true, true, 0);
this.show_all ();
}
internal DataWindow (string message, string backtrace) {
GLib.Object (type: Gtk.WindowType.TOPLEVEL,
window_position: Gtk.WindowPosition.CENTER,
message: message,
backtrace: backtrace);
this.create_content ();
}
}
private class LogWindow : Gtk.Window {
private Manager manager;
private void clear_list () {
this.manager.clear_list ();
this.destroy ();
}
#if HAVE_EXECINFO_H
private void row_activated (Gtk.TreePath path, Gtk.TreeViewColumn column) {
Gtk.TreeIter iter;
if (this.manager.list_store.get_iter (out iter, path)) {
string message;
string backtrace;
this.manager.list_store.get(iter,
TreeCells.MESSAGE, out message,
TreeCells.BACKTRACE, out backtrace, -1);
DataWindow data_window = new DataWindow (message, backtrace);
data_window.show ();
}
}
#endif
private void create_content () {
this.title = "Midori - DevPet";
this.set_default_size (500, 250);
this.destroy.connect (this.manager.log_window_closed);
Gtk.VBox vbox = new Gtk.VBox (false, 1);
this.add (vbox);
#if HAVE_EXECINFO_H
Gtk.Label label = new Gtk.Label (_("Double click for more information"));
vbox.pack_start (label, false, false, 0);
#endif
Gtk.ScrolledWindow scroll_windows = new Gtk.ScrolledWindow (null, null);
scroll_windows.set_policy (Gtk.PolicyType.NEVER , Gtk.PolicyType.AUTOMATIC);
scroll_windows.set_shadow_type (Gtk.ShadowType.ETCHED_IN);
Gtk.Button clear = new Gtk.Button.from_stock ("gtk-clear");
clear.clicked.connect (this.clear_list);
vbox.pack_start (scroll_windows, true, true, 0);
vbox.pack_start (clear, false, false, 0);
Gtk.TreeView treeview = new Gtk.TreeView.with_model (this.manager.list_store);
scroll_windows.add (treeview);
treeview.insert_column_with_attributes (
-1, "Type",
new Gtk.CellRendererPixbuf (), "stock-id", TreeCells.STOCK);
treeview.insert_column_with_attributes (
-1, "Message",
new Gtk.CellRendererText (), "text", TreeCells.MESSAGE);
#if HAVE_EXECINFO_H
treeview.row_activated.connect (this.row_activated);
#endif
this.show_all ();
}
internal LogWindow (Manager manager) {
GLib.Object (type: Gtk.WindowType.TOPLEVEL,
window_position: Gtk.WindowPosition.CENTER);
this.manager = manager;
this.create_content ();
}
}
private class Manager : Midori.Extension {
public Gtk.ListStore list_store;
private Gtk.StatusIcon? trayicon = null;
private LogWindow? log_window;
private GLib.LogFunc default_log_func;
private GLib.LogLevelFlags icon_flag = GLib.LogLevelFlags.LEVEL_DEBUG;
public void clear_list() {
this.icon_flag = GLib.LogLevelFlags.LEVEL_DEBUG;
if(this.trayicon != null)
this.trayicon.set_visible (false);
this.list_store.clear ();
}
public void log_window_closed () {
this.log_window = null;
}
private unowned string get_stock_from_log_level (GLib.LogLevelFlags flags) {
if ((flags & LogLevelFlags.LEVEL_CRITICAL) == flags || (flags & LogLevelFlags.LEVEL_ERROR) == flags) {
return Gtk.Stock.DIALOG_ERROR;
} else if ((flags & LogLevelFlags.LEVEL_WARNING) == flags) {
return Gtk.Stock.DIALOG_WARNING;
}
return Gtk.Stock.DIALOG_INFO;
}
private void ensure_trayicon() {
if(this.trayicon != null)
return;
this.trayicon = new Gtk.StatusIcon ();
this.trayicon.set_tooltip_text ("Midori - DevPet");
this.trayicon.activate.connect(this.show_error_log);
}
private void log_handler(string? domain, GLib.LogLevelFlags flags, string message) {
Gtk.TreeIter iter;
unowned string stock = this.get_stock_from_log_level (flags);
this.ensure_trayicon();
if (flags < this.icon_flag) {
this.icon_flag = flags;
this.trayicon.set_from_stock (stock);
}
#if HAVE_EXECINFO_H
string bt = "";
void* buffer[100];
int num = Linux.backtrace (buffer, 100);
/* Upstream bug: https://git.gnome.org/browse/vala/commit/?id=f402af94e8471c8314ee7a312260a776e4d6fbe2 */
unowned string[] symbols = Midori.Linux.backtrace_symbols (buffer, num);
if (symbols != null) {
/* we don't need the first three lines */
for (int i = 3; i < num; i++) {
bt += "\r\n%s".printf(symbols[i]);
}
}
#endif
this.list_store.append (out iter);
this.list_store.set (iter,
TreeCells.MESSAGE, message,
#if HAVE_EXECINFO_H
TreeCells.BACKTRACE, bt,
#endif
TreeCells.STOCK, stock);
this.trayicon.set_visible (true);
}
private void show_error_log () {
if (this.log_window == null) {
this.log_window = new LogWindow (this);
this.log_window.show ();
} else {
if (this.log_window.is_active) {
this.log_window.hide ();
} else {
this.log_window.present ();
}
}
}
private void activated (Midori.App app) {
this.default_log_func = GLib.Log.default_handler;
GLib.Log.set_default_handler (this.log_handler);
if (this.trayicon != null) {
int length = 0;
this.list_store.foreach((model, path, iter) => {
length++;
return false;
});
if (length > 0) {
this.trayicon.set_visible (true);
}
}
}
private void deactivated () {
if (this.trayicon != null)
this.trayicon.set_visible (false);
GLib.Log.set_default_handler (this.default_log_func);
}
internal Manager () {
GLib.Object (name: _("DevPet"),
description: _("This extension shows glib error messages in systray."),
version: "0.1",
authors: "André Stösel <andre@stoesel.de>");
this.list_store = new Gtk.ListStore (TreeCells.COUNT, typeof(string), typeof(string), typeof (string));
this.activate.connect (this.activated);
this.deactivate.connect (this.deactivated);
}
}
}
public Midori.Extension extension_init () {
theme = Gtk.IconTheme.get_default ();
return new DevPet.Manager ();
}

View file

@ -0,0 +1,72 @@
/*
Copyright (C) 2014 James Axl <bilimish@yandex.ru>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
namespace DomainHotkeys {
class Manager : Midori.Extension {
internal Manager () {
GLib.Object (name: _("Domain Hotkeys"),
description: _("Add www. and .com/.country_domain and proceed with Ctrl+Enter/Shift+Enter"),
version: "0.1" + Midori.VERSION_SUFFIX,
authors: "James Axl <bilimish@yandex.ru>");
activate.connect (this.activated);
deactivate.connect (this.deactivated);
}
bool key_press_event (Midori.LocationAction action, Gdk.EventKey event_key) {
if (event_key.keyval == Gdk.keyval_from_name ("Return")) {
if ((bool)(event_key.state & Gdk.ModifierType.CONTROL_MASK)) {
location_action_submit_uri_with_suffix (action, ".com");
return true;
} else if((bool)(event_key.state & Gdk.ModifierType.SHIFT_MASK)) {
var domain = C_("Domain", ".com");
location_action_submit_uri_with_suffix (action, domain);
return true;
}
}
return false;
}
void location_action_submit_uri_with_suffix (Midori.LocationAction action, string suffix) {
var url = action.get_text ();
string completed_url = "www." + url + suffix;
action.submit_uri (completed_url, false);
}
void browser_added (Midori.Browser browser) {
var action_group = browser.get_action_group ();
var action = action_group.get_action ("Location") as Midori.LocationAction;
action.key_press_event.connect (key_press_event);
}
void activated (Midori.App app) {
foreach (var browser in app.get_browsers ())
browser_added (browser);
app.add_browser.connect (browser_added);
}
void browser_removed (Midori.Browser browser) {
var action_group = browser.get_action_group ();
var action = action_group.get_action ("Location") as Midori.LocationAction;
action.key_press_event.disconnect (key_press_event);
}
void deactivated () {
var app = get_app ();
app.add_browser.disconnect (browser_added);
foreach (var browser in app.get_browsers ())
browser_removed (browser);
}
}
}
public Midori.Extension extension_init () {
return new DomainHotkeys.Manager ();
}

View file

@ -9,12 +9,6 @@
See the file COPYING for the full license text.
*/
using Gtk;
using Soup;
using Katze;
using Midori;
using WebKit;
namespace EDM {
#if !HAVE_WIN32
[DBus (name = "net.launchpad.steadyflow.App")]
@ -33,7 +27,7 @@ namespace EDM {
internal Manager manager;
private class Manager : GLib.Object {
private CookieJar cookie_jar;
private Soup.CookieJar cookie_jar;
private GLib.PtrArray download_managers = new GLib.PtrArray ();
public bool download_requested (Midori.View view, WebKit.Download download) {
@ -41,11 +35,16 @@ namespace EDM {
if (download_type == Midori.DownloadType.SAVE) {
var dlReq = new DownloadRequest ();
dlReq.uri = download.get_uri ();
#if HAVE_WEBKIT2
dlReq.uri = download.request.get_uri ();
weak Soup.MessageHeaders headers = download.request.get_http_headers ();
#else
dlReq.uri = download.get_uri ();
var request = download.get_network_request ();
var message = request.get_message ();
weak MessageHeaders headers = message.request_headers;
weak Soup.MessageHeaders headers = message.request_headers;
#endif
dlReq.auth = headers.get ("Authorization");
dlReq.referer = headers.get ("Referer");
@ -102,8 +101,12 @@ namespace EDM {
}
construct {
#if HAVE_WEBKIT2
var session= new Session ();
#else
var session = WebKit.get_default_session ();
this.cookie_jar = session.get_feature (typeof (CookieJar)) as CookieJar;
#endif
this.cookie_jar = session.get_feature (typeof (Soup.CookieJar)) as Soup.CookieJar;
}
}
@ -119,8 +122,8 @@ namespace EDM {
public void handle_exception (GLib.Error error) {
string ext_name;
this.get ("name",out ext_name);
var dialog = new MessageDialog (null, DialogFlags.MODAL,
MessageType.ERROR, ButtonsType.CLOSE,
var dialog = new Gtk.MessageDialog (null, Gtk.DialogFlags.MODAL,
Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE,
_("An error occurred when attempting to download a file with the following plugin:\n" +
"%s\n\n" +
"Error:\n%s\n\n" +
@ -137,32 +140,49 @@ namespace EDM {
#if !HAVE_WIN32
private class Aria2 : ExternalDownloadManager {
public override bool download (DownloadRequest dlReq) {
var url = value_array_new ();
value_array_insert (url, 0, typeof (string), dlReq.uri);
var url = Soup.value_array_new ();
Soup.value_array_insert (url, 0, typeof (string), dlReq.uri);
GLib.HashTable<string, GLib.Value?> options = value_hash_new ();
GLib.HashTable<string, GLib.Value?> options = Soup.value_hash_new ();
var referer = new GLib.Value (typeof (string));
referer.set_string (dlReq.referer);
options.insert ("referer", referer);
var headers = value_array_new ();
var headers = Soup.value_array_new ();
if (dlReq.cookie_header != null) {
value_array_insert (headers, 0, typeof (string), "Cookie: %s".printf(dlReq.cookie_header));
Soup.value_array_insert (headers, 0, typeof (string), "Cookie: " + dlReq.cookie_header);
}
if (headers.n_values > 0)
options.insert ("header", headers);
var message = XMLRPC.request_new ("http://127.0.0.1:6800/rpc",
var message = Soup.XMLRPC.request_new ("http://127.0.0.1:6800/rpc",
"aria2.addUri",
typeof (ValueArray), url,
typeof(HashTable), options);
var session = new SessionSync ();
var session = new Soup.SessionSync ();
session.send_message (message);
/* Check if the plug-in actually recieved an reply.
* The parse method do not warns us about it. And the exception
* never is launched.*/
if (message.status_code != 200) {
var dialog = new Gtk.MessageDialog (null, Gtk.DialogFlags.MODAL,
Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE,
"%s",
_("The plug-in was unable to connect with aria2:\n" +
"Please make sure that aria2 is running with rpc enabled ie: aria2c --enable-rpc\n" +
"If it's so, check it also is using the port 6800.\n" +
"Lastly Check the configuration of your firewall.\n" +
"Whitelist aria2 and the port 6800 if they aren't."
));
dialog.response.connect ((a) => { dialog.destroy (); });
dialog.run ();
}
try {
Value v;
XMLRPC.parse_method_response ((string) message.response_body.flatten ().data, -1, out v);
Soup.XMLRPC.parse_method_response ((string) message.response_body.flatten ().data, -1, out v);
return true;
} catch (Error e) {
this.handle_exception (e);
@ -212,7 +232,7 @@ namespace EDM {
#endif
private class CommandLinePreferences : Gtk.Dialog {
protected Entry input;
protected Gtk.Entry input;
protected CommandLine commandline;
public CommandLinePreferences(CommandLine cl) {
@ -234,20 +254,20 @@ namespace EDM {
private void response_cb (Gtk.Dialog source, int response_id) {
switch (response_id) {
case ResponseType.APPLY:
case Gtk.ResponseType.APPLY:
this.commandline.set_string ("commandline", this.input.get_text ());
this.commandline.update_description (this.commandline.get_app ());
this.destroy ();
break;
case ResponseType.CANCEL:
case Gtk.ResponseType.CANCEL:
this.destroy ();
break;
}
}
private void create_widgets () {
Label text = new Label (_("Command:"));
this.input = new Entry ();
Gtk.Label text = new Gtk.Label (_("Command:"));
this.input = new Gtk.Entry ();
this.input.set_text (this.commandline.get_string ("commandline"));
@ -260,8 +280,8 @@ namespace EDM {
this.vbox.pack_start (this.input, false, true, 0);
#endif
this.add_button (Gtk.STOCK_CANCEL, ResponseType.CANCEL);
this.add_button (Gtk.STOCK_APPLY, ResponseType.APPLY);
this.add_button (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL);
this.add_button (Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY);
this.show_all ();
}
@ -273,15 +293,15 @@ namespace EDM {
dialog.show ();
}
private string replace_quoted (string context, string replace, string? with) {
return context.replace(replace, with != null ? GLib.Shell.quote(with) : "\'\'");
}
public override bool download (DownloadRequest dlReq) {
try {
string cmd = this.get_string ("commandline");
cmd = cmd.replace("{REFERER}", GLib.Shell.quote (dlReq.referer));
if (dlReq.cookie_header != null) {
cmd = cmd.replace("{COOKIES}", GLib.Shell.quote ("Cookie: " + dlReq.cookie_header));
} else {
cmd = cmd.replace("{COOKIES}", "\'\'");
}
cmd = replace_quoted(cmd, "{REFERER}", dlReq.referer);
cmd = replace_quoted(cmd, "{COOKIES}", dlReq.cookie_header != null ? "Cookie: " + dlReq.cookie_header : null);
cmd = cmd.replace("{URL}", GLib.Shell.quote (dlReq.uri));
GLib.Process.spawn_command_line_async (cmd);
return true;
@ -311,7 +331,7 @@ namespace EDM {
internal CommandLine () {
#if HAVE_WIN32
string default_commandline = "\"%s\\FlashGet\\flashget.exe\" {URL}".printf (Environment.get_variable ("ProgramFiles"));
#elif HAVE_FREEBSD
#elif HAVE_FREEBSD || HAVE_DRAGONFLY
string default_commandline = "fetch HTTP_REFERER={REFERER} {URL}";
#else
string default_commandline = "wget --no-check-certificate --referer={REFERER} --header={COOKIES} {URL}";
@ -344,4 +364,3 @@ public Katze.Array extension_init () {
extensions.add_item (new EDM.CommandLine ());
return extensions;
}

View file

@ -359,17 +359,9 @@ feed_panel_cursor_or_row_changed_cb (GtkTreeView* treeview,
g_assert (KATZE_IS_ARRAY (parent));
if (added)
{
#if GLIB_CHECK_VERSION (2, 26, 0)
GDateTime* date = g_date_time_new_from_unix_local (added);
gchar* pretty = g_date_time_format (date, "%c");
g_date_time_unref (date);
#else
static gchar date_fmt[512];
const struct tm *tm = localtime (&added);
/* Some GCC versions falsely complain about "%c" */
strftime (date_fmt, sizeof (date_fmt), "%c", tm);
gchar* pretty = g_strdup (date_fmt);
#endif
/* i18n: The local date a feed was last updated */
gchar* last_updated = g_strdup_printf (C_("Feed", "Last updated: %s."), pretty);
@ -448,7 +440,6 @@ feed_panel_open_in_tab_activate_cb (GtkWidget* menuitem,
{
KatzeItem* item;
const gchar* uri;
guint n;
item = (KatzeItem*)g_object_get_data (G_OBJECT (menuitem), "KatzeItem");
@ -675,8 +666,6 @@ feed_panel_get_toolbar (MidoriViewable* viewable)
GtkToolItem* toolitem;
toolbar = gtk_toolbar_new ();
gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_BOTH_HORIZ);
gtk_toolbar_set_icon_size (GTK_TOOLBAR (toolbar), GTK_ICON_SIZE_BUTTON);
panel->toolbar = toolbar;
toolitem = gtk_tool_button_new_from_stock (GTK_STOCK_ADD);
gtk_widget_set_tooltip_text (GTK_WIDGET (toolitem), _("Add new feed"));

View file

@ -25,7 +25,11 @@ rss_is_valid (FeedParser* fparser)
{
if ((str = xmlGetProp (node, BAD_CAST "version")))
{
valid = !xmlStrcmp (str, BAD_CAST "2.0");
if (!xmlStrcmp (str, BAD_CAST "2.0") || !xmlStrcmp (str, BAD_CAST "0.92"))
valid = TRUE;
else
valid = FALSE;
xmlFree (str);
if (valid)

View file

@ -66,6 +66,7 @@ typedef struct
KatzeNetRequest* request;
} KatzeNetPriv;
#ifndef HAVE_WEBKIT2
static void
katze_net_priv_free (KatzeNetPriv* priv)
{
@ -77,41 +78,6 @@ katze_net_priv_free (KatzeNetPriv* priv)
g_slice_free (KatzeNetPriv, priv);
}
#if !WEBKIT_CHECK_VERSION (1, 3, 13)
gchar*
katze_net_get_cached_path (KatzeNet* net,
const gchar* uri,
const gchar* subfolder)
{
gchar* checksum;
gchar* extension;
gchar* cached_filename;
gchar* cached_path;
if (uri == NULL)
return NULL;
checksum = g_compute_checksum_for_string (G_CHECKSUM_MD5, uri, -1);
extension = g_strrstr (uri, ".");
cached_filename = g_strdup_printf ("%s%s", checksum,
extension ? extension : "");
g_free (checksum);
if (subfolder)
{
gchar* cache_path = g_build_filename (midori_paths_get_cache_dir_for_reading (), subfolder, NULL);
katze_mkdir_with_parents (cache_path, 0700);
cached_path = g_build_filename (cache_path, cached_filename, NULL);
g_free (cache_path);
}
else
cached_path = g_build_filename (midori_paths_get_cache_dir (), cached_filename, NULL);
g_free (cached_filename);
return cached_path;
}
#endif
static void
katze_net_got_body_cb (SoupMessage* msg,
KatzeNetPriv* priv);
@ -120,7 +86,6 @@ static void
katze_net_got_headers_cb (SoupMessage* msg,
KatzeNetPriv* priv)
{
#ifndef HAVE_WEBKIT2
KatzeNetRequest* request = priv->request;
switch (msg->status_code)
@ -141,7 +106,6 @@ katze_net_got_headers_cb (SoupMessage* msg,
g_signal_handlers_disconnect_by_func (msg, katze_net_got_body_cb, priv);
soup_session_cancel_message (webkit_get_default_session (), msg, 1);
}
#endif
}
static void
@ -167,42 +131,6 @@ katze_net_finished_cb (SoupMessage* msg,
katze_net_priv_free (priv);
}
static gboolean
katze_net_local_cb (KatzeNetPriv* priv)
{
KatzeNetRequest* request = priv->request;
gchar* filename = g_filename_from_uri (request->uri, NULL, NULL);
if (!filename || g_access (filename, F_OK) != 0)
{
request->status = KATZE_NET_NOT_FOUND;
if (priv->status_cb)
priv->status_cb (request, priv->user_data);
}
else if (!(priv->status_cb && !priv->status_cb (request, priv->user_data))
&& priv->transfer_cb)
{
gchar* contents = NULL;
gsize length;
request->status = KATZE_NET_VERIFIED;
if (!g_file_get_contents (filename, &contents, &length, NULL))
{
request->status = KATZE_NET_FAILED;
}
else
{
request->status = KATZE_NET_DONE;
request->data = contents;
request->length = length;
}
priv->transfer_cb (request, priv->user_data);
}
g_free (filename);
katze_net_priv_free (priv);
return FALSE;
}
static gboolean
katze_net_default_cb (KatzeNetPriv* priv)
{
@ -215,6 +143,7 @@ katze_net_default_cb (KatzeNetPriv* priv)
katze_net_priv_free (priv);
return FALSE;
}
#endif
/**
* katze_net_load_uri:
@ -279,9 +208,6 @@ katze_net_load_uri (KatzeNet* net,
return;
}
if (g_str_has_prefix (uri, "file://"))
g_idle_add ((GSourceFunc)katze_net_local_cb, priv);
else
g_idle_add ((GSourceFunc)katze_net_default_cb, priv);
#endif
}

View file

@ -12,11 +12,6 @@
#ifndef __KATZE_NET_H__
#define __KATZE_NET_H__
#ifndef HAVE_WEBKIT2
#include <webkit/webkit.h>
#else
#include <webkit2/webkit2.h>
#endif
#include "katze-utils.h"
G_BEGIN_DECLS
@ -71,13 +66,6 @@ katze_net_load_uri (KatzeNet* net,
KatzeNetTransferCb transfer_cb,
gpointer user_data);
#if !WEBKIT_CHECK_VERSION (1, 3, 13)
gchar*
katze_net_get_cached_path (KatzeNet* net,
const gchar* uri,
const gchar* subfolder);
#endif
G_END_DECLS
#endif /* __KATZE_NET_H__ */

View file

@ -13,6 +13,7 @@
#include "feed-atom.h"
#include "feed-rss.h"
#include "katze-net.h"
#include <midori/midori.h>
#define EXTENSION_NAME "Feed Panel"
@ -365,7 +366,7 @@ panel_add_feed_cb (FeedPanel* panel,
gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
gtk_entry_set_text (GTK_ENTRY (entry), "");
gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
gtk_container_add (GTK_CONTAINER(gtk_dialog_get_content_area (GTK_DIALOG (dialog))), hbox);
gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area (GTK_DIALOG (dialog))), hbox, FALSE, TRUE, 0);
gtk_widget_show_all (hbox);
gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);

94
extensions/flummi.vala Normal file
View file

@ -0,0 +1,94 @@
/*
Copyright (C) 2013 André Stösel <andre@stoesel.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
namespace Flummi {
private class Manager : Midori.Extension {
private bool bounce () {
try {
Midori.App app = this.get_app ();
Midori.Browser? browser = app.browser;
if (browser == null || browser.tab == null) {
return true;
}
Midori.Database database = new Midori.Database ("flummi.db");
unowned Sqlite.Database db = database.db;
string sqlcmd = "SELECT id, once, command FROM tasks ORDER BY id;";
Sqlite.Statement stmt;
if (db.prepare_v2 (sqlcmd, -1, out stmt, null) != Sqlite.OK) {
GLib.critical ("Failed to select from database: %s", db.errmsg ());
return false;
}
int result = stmt.step ();
if (!(result == Sqlite.DONE || result == Sqlite.ROW)) {
GLib.critical ("Failed to select from database: %s", db.errmsg ());
return false;
}
Sqlite.Statement del_stmt;
sqlcmd = "DELETE FROM `tasks` WHERE id = :task_id;";
if (db.prepare_v2 (sqlcmd, -1, out del_stmt, null) != Sqlite.OK) {
GLib.critical ("Failed to update database: %s", db.errmsg ());
return false;
}
while (result == Sqlite.ROW) {
int64 id = stmt.column_int64 (0);
int64 once = stmt.column_int64 (1);
string command = stmt.column_text (2);
string[] commands = { command };
if (!app.send_command (commands)) {
GLib.critical ("Command failed: %s", command);
return false;
}
if (once > 0) {
del_stmt.bind_int64 (del_stmt.bind_parameter_index (":task_id"), id);
if (del_stmt.step () != Sqlite.DONE) {
GLib.critical ("Failed to delete record %lf.\nError: %s", id, db.errmsg ());
return false;
}
}
result = stmt.step ();
}
} catch (Midori.DatabaseError schema_error) {
GLib.error (schema_error.message);
}
return false;
}
private void activated (Midori.App app) {
GLib.Idle.add (this.bounce);
}
internal Manager () {
GLib.Object (name: _("Flummi"),
description: _("This extension provides a task queue for update jobs or recurring events."),
version: "0.1",
authors: "André Stösel <andre@stoesel.de>");
this.activate.connect (this.activated);
}
}
}
public Midori.Extension extension_init () {
return new Flummi.Manager ();
}

View file

@ -18,17 +18,12 @@
#include <unistd.h>
#endif
#if WEBKIT_CHECK_VERSION (1, 3, 1)
#define FORMHISTORY_USE_GDOM 1
#else
#define FORMHISTORY_USE_JS 1
#endif
#define MAXPASSSIZE 64
typedef struct
{
MidoriDatabase* database;
sqlite3* db;
#ifdef FORMHISTORY_USE_GDOM
WebKitDOMElement* element;
int completion_timeout;
GtkTreeModel* completion_model;
@ -36,9 +31,6 @@ typedef struct
GtkWidget* popup;
gchar* oldkeyword;
glong selection_index;
#else
gchar* jsforms;
#endif
gchar* master_password;
int master_password_canceled;
} FormHistoryPriv;
@ -64,11 +56,9 @@ formhistory_setup_suggestions (WebKitWebView* web_view,
JSContextRef js_context,
MidoriExtension* extension);
#ifdef FORMHISTORY_USE_GDOM
void
formhistory_suggestions_hide_cb (WebKitDOMElement* element,
WebKitDOMEvent* dom_event,
FormHistoryPriv* priv);
#endif
#endif

View file

@ -8,7 +8,6 @@
version 2.1 of the License, or (at your option) any later version.
*/
#include "formhistory-frontend.h"
#ifdef FORMHISTORY_USE_GDOM
#define COMPLETION_DELAY 200
FormHistoryPriv*
@ -393,14 +392,12 @@ formhistory_DOMContentLoaded_cb (WebKitDOMElement* window,
for (i = 0; i < webkit_dom_node_list_get_length (inputs); i++)
{
WebKitDOMNode* element = webkit_dom_node_list_item (inputs, i);
#if WEBKIT_CHECK_VERSION (1, 6, 1)
gchar* autocomplete = webkit_dom_html_input_element_get_autocomplete (
WEBKIT_DOM_HTML_INPUT_ELEMENT (element));
gboolean off = !g_strcmp0 (autocomplete, "off");
g_free (autocomplete);
if (off)
continue;
#endif
g_object_set_data (G_OBJECT (element), "doc", doc);
g_object_set_data (G_OBJECT (element), "webview", web_view);
@ -460,11 +457,7 @@ formhistory_setup_suggestions (WebKitWebView* web_view,
void
formhistory_private_destroy (FormHistoryPriv *priv)
{
if (priv->db)
{
sqlite3_close (priv->db);
priv->db = NULL;
}
katze_object_assign (priv->database, NULL);
katze_assign (priv->oldkeyword, NULL);
gtk_widget_destroy (priv->popup);
priv->popup = NULL;
@ -506,9 +499,9 @@ formhistory_construct_popup_gui (FormHistoryPriv* priv)
column = gtk_tree_view_column_new_with_attributes ("suggestions", renderer, "text", 0, NULL);
gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
priv->popup = popup;
priv->element = NULL;
g_signal_connect (treeview, "button-press-event",
G_CALLBACK (formhistory_suggestion_selected_cb), priv);
return TRUE;
}
#endif

View file

@ -1,141 +0,0 @@
/*
Copyright (C) 2009-2012 Alexander Butenko <a.butenka@gmail.com>
Copyright (C) 2009-2012 Christian Dywan <christian@twotoasts.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
*/
#include "formhistory-frontend.h"
#ifdef FORMHISTORY_USE_JS
FormHistoryPriv*
formhistory_private_new ()
{
FormHistoryPriv* priv = g_slice_new (FormHistoryPriv);
return priv;
}
gboolean
formhistory_construct_popup_gui (FormHistoryPriv* priv)
{
gchar* autosuggest;
gchar* style;
guint i;
gchar* file;
file = midori_app_find_res_filename ("autosuggestcontrol.js");
if (!g_file_get_contents (file, &autosuggest, NULL, NULL))
{
g_free (file);
return FALSE;
}
g_strchomp (autosuggest);
katze_assign (file, midori_app_find_res_filename ("autosuggestcontrol.css"));
if (!g_file_get_contents (file, &style, NULL, NULL))
{
g_free (file);
return FALSE;
}
g_strchomp (style);
g_free (file);
i = 0;
while (style[i])
{
if (style[i] == '\n')
style[i] = ' ';
i++;
}
priv->jsforms = g_strdup_printf (
"%s"
"window.addEventListener ('DOMContentLoaded',"
"function () {"
" if (document.getElementById('formhistory'))"
" return;"
" if (!initSuggestions ())"
" return;"
" var mystyle = document.createElement('style');"
" mystyle.setAttribute('type', 'text/css');"
" mystyle.setAttribute('id', 'formhistory');"
" mystyle.appendChild(document.createTextNode('%s'));"
" var head = document.getElementsByTagName('head')[0];"
" if (head) head.appendChild(mystyle);"
"}, true);",
autosuggest,
style);
g_strstrip (priv->jsforms);
g_free (style);
g_free (autosuggest);
return TRUE;
}
void
formhistory_setup_suggestions (WebKitWebView* web_view,
JSContextRef js_context,
MidoriExtension* extension)
{
GString* suggestions;
FormHistoryPriv* priv;
static sqlite3_stmt* stmt;
const char* sqlcmd;
const unsigned char* key;
const unsigned char* value;
gint result, pos;
priv = g_object_get_data (G_OBJECT (extension), "priv");
if (!priv->db)
return;
if (!stmt)
{
sqlcmd = "SELECT DISTINCT group_concat(value,'\",\"'), field FROM forms \
GROUP BY field ORDER BY field";
sqlite3_prepare_v2 (priv->db, sqlcmd, strlen (sqlcmd) + 1, &stmt, NULL);
}
result = sqlite3_step (stmt);
if (result != SQLITE_ROW)
{
if (result == SQLITE_ERROR)
g_print (_("Failed to select suggestions\n"));
sqlite3_reset (stmt);
return;
}
suggestions = g_string_new (
"function FormSuggestions(eid) { "
"arr = new Array();");
while (result == SQLITE_ROW)
{
pos++;
value = sqlite3_column_text (stmt, 0);
key = sqlite3_column_text (stmt, 1);
if (value)
{
g_string_append_printf (suggestions, " arr[\"%s\"] = [\"%s\"]; ",
(gchar*)key, (gchar*)value);
}
result = sqlite3_step (stmt);
}
g_string_append (suggestions, "this.suggestions = arr[eid]; }");
g_string_append (suggestions, priv->jsforms);
sokoke_js_script_eval (js_context, suggestions->str, NULL);
g_string_free (suggestions, TRUE);
}
void
formhistory_private_destroy (FormHistoryPriv *priv)
{
if (priv->db)
{
sqlite3_close (priv->db);
priv->db = NULL;
}
katze_assign (priv->jsforms, NULL);
g_slice_free (FormHistoryPriv, priv);
}
#endif

View file

@ -117,13 +117,13 @@ formhistory_check_master_password (GtkWidget* parent,
label = gtk_label_new (_("Master password required\n"
"to open password database"));
gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
gtk_container_add (GTK_CONTAINER (content_area), hbox);
gtk_box_pack_start (GTK_BOX (content_area), hbox, FALSE, TRUE, 0);
entry = gtk_entry_new ();
g_object_set (entry, "truncate-multiline", TRUE, NULL);
gtk_entry_set_visibility(GTK_ENTRY (entry),FALSE);
gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
gtk_container_add (GTK_CONTAINER (content_area), entry);
gtk_box_pack_start (GTK_BOX (content_area), entry, FALSE, TRUE, 0);
gtk_widget_show_all (entry);
gtk_widget_show_all (hbox);
@ -221,9 +221,7 @@ formhistory_navigation_decision_cb (WebKitWebView* web_view,
js_context = webkit_web_frame_get_global_context (web_frame);
value = sokoke_js_script_eval (js_context, script, NULL);
#ifdef FORMHISTORY_USE_GDOM
formhistory_suggestions_hide_cb (NULL, NULL, priv);
#endif
if (value && *value)
{
gchar** inputs = g_strsplit (value, "|||", 0);
@ -235,7 +233,6 @@ formhistory_navigation_decision_cb (WebKitWebView* web_view,
{
if (strcmp (parts[2], "password"))
formhistory_update_database (priv->db, NULL, parts[0], parts[1]);
#if WEBKIT_CHECK_VERSION (1, 3, 8)
else
{
#if 0
@ -256,7 +253,6 @@ formhistory_navigation_decision_cb (WebKitWebView* web_view,
g_object_set_data (G_OBJECT (web_view), "FormHistoryPasswordEntry", entry);
#endif
}
#endif
}
g_strfreev (parts);
i++;
@ -287,7 +283,6 @@ formhistory_window_object_cleared_cb (WebKitWebView* web_view,
formhistory_setup_suggestions (web_view, js_context, extension);
#if WEBKIT_CHECK_VERSION (1, 3, 8)
entry = g_object_get_data (G_OBJECT (web_view), "FormHistoryPasswordEntry");
if (entry)
{
@ -301,10 +296,8 @@ formhistory_window_object_cleared_cb (WebKitWebView* web_view,
_("Never for this page"), GTK_RESPONSE_CANCEL, NULL);
g_object_set_data (G_OBJECT (web_view), "FormHistoryPasswordEntry", NULL);
}
#endif
}
#if WEBKIT_CHECK_VERSION (1, 3, 8)
static gchar*
formhistory_decrypt (const gchar* data,
const gchar* password)
@ -391,7 +384,6 @@ formhistory_frame_loaded_cb (WebKitWebView* web_view,
formhistory_fill_login_data (js_context, priv, data);
g_free (data);
}
#endif
static void
formhistory_deactivate_cb (MidoriExtension* extension,
@ -408,11 +400,8 @@ formhistory_add_tab_cb (MidoriBrowser* browser,
G_CALLBACK (formhistory_window_object_cleared_cb), extension);
g_signal_connect (web_view, "navigation-policy-decision-requested",
G_CALLBACK (formhistory_navigation_decision_cb), extension);
#if WEBKIT_CHECK_VERSION (1, 3, 8)
g_signal_connect (web_view, "onload-event",
G_CALLBACK (formhistory_frame_loaded_cb), extension);
#endif
}
static void
@ -460,10 +449,8 @@ formhistory_deactivate_tab (MidoriView* view,
web_view, formhistory_window_object_cleared_cb, extension);
g_signal_handlers_disconnect_by_func (
web_view, formhistory_navigation_decision_cb, extension);
#if WEBKIT_CHECK_VERSION (1, 3, 8)
g_signal_handlers_disconnect_by_func (
web_view, formhistory_frame_loaded_cb, extension);
#endif
}
static void
@ -502,44 +489,31 @@ static FormHistoryPriv*
formhistory_new (const gchar* config_dir)
{
gchar* filename;
sqlite3* db;
char* errmsg = NULL, *errmsg2 = NULL;
GError* error = NULL;
FormHistoryPriv* priv = formhistory_private_new ();
priv->master_password = NULL;
priv->master_password_canceled = 0;
formhistory_construct_popup_gui (priv);
if (config_dir == NULL)
{
priv->db = NULL;
return priv;
}
filename = g_build_filename (config_dir, "forms.db", NULL);
if (sqlite3_open (filename, &db) != SQLITE_OK)
{
g_warning (_("Failed to open database: %s\n"), sqlite3_errmsg (db));
sqlite3_close (db);
}
priv->database = midori_database_new (filename, &error);
g_free (filename);
if ((sqlite3_exec (db, "CREATE TABLE IF NOT EXISTS "
"forms (domain text, field text, value text)",
NULL, NULL, &errmsg) == SQLITE_OK))
if (error != NULL)
{
sqlite3_exec (db,
/* "PRAGMA synchronous = OFF; PRAGMA temp_store = MEMORY" */
"PRAGMA count_changes = OFF; PRAGMA journal_mode = TRUNCATE;",
NULL, NULL, &errmsg);
priv->db = db;
}
else
{
if (errmsg)
{
g_critical (_("Failed to execute database statement: %s\n"), errmsg);
sqlite3_free (errmsg);
if (errmsg2)
{
g_critical (_("Failed to execute database statement: %s\n"), errmsg2);
sqlite3_free (errmsg2);
}
}
sqlite3_close (db);
g_critical ("%s", error->message);
g_error_free (error);
priv->db = NULL;
return priv;
}
priv->db = midori_database_get_db (MIDORI_DATABASE (priv->database));
g_warn_if_fail (priv->db != NULL);
return priv;
}
@ -625,7 +599,7 @@ formhistory_preferences_cb (MidoriExtension* extension)
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbox),
!midori_extension_get_boolean (extension, "always-load"));
g_object_set_data (G_OBJECT (dialog), "always-load-checkbox", checkbox);
gtk_container_add (GTK_CONTAINER (content_area), checkbox);
gtk_box_pack_start (GTK_BOX (content_area), checkbox, FALSE, TRUE, 0);
/* FIXME: Add pref to disable password manager */
g_signal_connect (dialog,

View file

@ -1,5 +1,5 @@
/*
Copyright (C) 2010-2011 André Stösel <andre@stoesel.de>
Copyright (C) 2010-2013 André Stösel <andre@stoesel.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
@ -9,11 +9,6 @@
See the file COPYING for the full license text.
*/
using Gtk;
using Gdk;
using WebKit;
using Midori;
namespace HistoryList {
enum TabTreeCells {
TREE_CELL_PIXBUF,
@ -50,6 +45,8 @@ namespace HistoryList {
Gtk.TreeViewColumn? column;
this.treeview.get_cursor (out path, out column);
if (path == null)
return;
unowned int[] indices = path.get_indices ();
int new_index = indices[0] + step;
@ -66,14 +63,16 @@ namespace HistoryList {
public abstract void make_update ();
public abstract void clean_up ();
public abstract void close_tab ();
}
private class TabWindow : HistoryWindow {
protected Gtk.HBox? hbox;
protected Gtk.VBox? vbox;
protected bool is_dirty = false;
protected Gtk.ScrolledWindow? scroll_windows;
protected void store_append_row (GLib.PtrArray list, Gtk.ListStore store, out Gtk.TreeIter iter) {
protected void store_append_row (GLib.PtrArray list, Gtk.ListStore store) {
for (var i = list.len; i > 0; i--) {
Midori.View view = list.index (i - 1) as Midori.View;
@ -82,6 +81,7 @@ namespace HistoryList {
unowned string title = view.get_display_title ();
Gtk.TreeIter iter;
store.append (out iter);
store.set (iter, TabTreeCells.TREE_CELL_PIXBUF, icon,
TabTreeCells.TREE_CELL_STRING, title,
@ -92,11 +92,32 @@ namespace HistoryList {
}
protected virtual void insert_rows (Gtk.ListStore store) {
Gtk.TreeIter iter;
unowned GLib.PtrArray list = this.browser.get_data<GLib.PtrArray> ("history-list-tab-history");
unowned GLib.PtrArray list_new = this.browser.get_data<GLib.PtrArray> ("history-list-tab-history-new");
store_append_row (list, store, out iter);
store_append_row (list_new, store, out iter);
store_append_row (list, store);
store_append_row (list_new, store);
}
protected void resize_treeview () {
Gtk.Requisition requisition;
int height;
int max_lines = 10;
#if HAVE_GTK3
requisition = Gtk.Requisition();
this.treeview.get_preferred_size(out requisition, null);
#else
this.treeview.size_request (out requisition);
#endif
Gtk.ListStore model = this.treeview.get_model () as Gtk.ListStore;
int length = model.iter_n_children(null);
if (length > max_lines) {
height = requisition.height / length * max_lines + 2;
} else {
height = requisition.height + 2;
}
this.scroll_windows.set_size_request (320, height);
this.resize (320, height);
}
public TabWindow (Midori.Browser browser) {
@ -107,10 +128,10 @@ namespace HistoryList {
this.hbox = new Gtk.HBox (false, 1);
var sw = new Gtk.ScrolledWindow (null, null);
sw.set_policy (PolicyType.NEVER , PolicyType.AUTOMATIC);
sw.set_shadow_type (ShadowType.ETCHED_IN);
this.hbox.pack_start (sw, true, true, 0);
this.scroll_windows = new Gtk.ScrolledWindow (null, null);
this.scroll_windows.set_policy (Gtk.PolicyType.NEVER , Gtk.PolicyType.AUTOMATIC);
this.scroll_windows.set_shadow_type (Gtk.ShadowType.ETCHED_IN);
this.hbox.pack_start (this.scroll_windows, true, true, 0);
var store = new Gtk.ListStore (TabTreeCells.TREE_CELL_COUNT,
typeof (Gdk.Pixbuf), typeof (string),
@ -121,39 +142,23 @@ namespace HistoryList {
this.vbox.pack_start (this.hbox, true, true, 0);
this.treeview = new Gtk.TreeView.with_model (store);
sw.add (treeview);
this.scroll_windows.add (treeview);
this.treeview.set_model (store);
this.treeview.set ("headers-visible", false);
this.treeview.insert_column_with_attributes (
-1, "Icon",
new CellRendererPixbuf (), "pixbuf", TabTreeCells.TREE_CELL_PIXBUF,
new Gtk.CellRendererPixbuf (), "pixbuf", TabTreeCells.TREE_CELL_PIXBUF,
"cell-background-gdk", TabTreeCells.TREE_CELL_BG);
this.treeview.insert_column_with_attributes (
-1, "Title",
new CellRendererText (), "text", TabTreeCells.TREE_CELL_STRING,
new Gtk.CellRendererText (), "text", TabTreeCells.TREE_CELL_STRING,
"foreground-gdk", TabTreeCells.TREE_CELL_FG,
"cell-background-gdk", TabTreeCells.TREE_CELL_BG);
this.show_all ();
Requisition requisition;
int height;
int max_lines = 10;
#if HAVE_GTK3
requisition = Requisition();
this.treeview.get_preferred_size(out requisition, null);
#else
this.treeview.size_request (out requisition);
#endif
int length = store.iter_n_children(null);
if (length > max_lines) {
height = requisition.height / length * max_lines + 2;
} else {
height = requisition.height + 2;
}
sw.set_size_request (320, height);
this.resize_treeview ();
}
public override void make_update () {
@ -163,13 +168,16 @@ namespace HistoryList {
Gtk.TreeViewColumn? column;
this.treeview.get_cursor (out path, out column);
if (path == null)
return;
var model = this.treeview.get_model () as Gtk.ListStore;
Gtk.TreeIter iter;
unowned Midori.View? view = null;
model.get_iter (out iter, path);
if (!model.get_iter (out iter, path))
return;
model.get (iter, TabTreeCells.TREE_CELL_POINTER, out view);
this.browser.set ("tab", view);
}
@ -188,6 +196,41 @@ namespace HistoryList {
this.is_dirty = false;
}
}
public override void close_tab () {
Gtk.TreePath? path;
Gtk.TreeViewColumn? column;
this.treeview.get_cursor (out path, out column);
Gtk.ListStore model = this.treeview.get_model () as Gtk.ListStore;
int length = model.iter_n_children(null);
if (length > 1) {
Gtk.TreeIter iter;
unowned Midori.View? view = null;
model.get_iter (out iter, path);
model.get (iter, TabTreeCells.TREE_CELL_POINTER, out view);
#if !HAVE_GTK3
/* removing the selected cursor causes a segfault when using GTK2 */
if (path.prev () == false)
path.next ();
this.treeview.set_cursor (path, column, false);
#endif
/*
FixMe: the retrun value of `Gtk.ListStore.remove` should be checked
Note: in some cases the return value of `Gtk.ListStore.remove` is wrong
*/
model.remove (iter);
this.browser.close_tab (view);
if (length > 2)
this.resize_treeview ();
else
this.hide ();
}
}
}
private class NewTabWindow : TabWindow {
@ -195,16 +238,15 @@ namespace HistoryList {
protected bool first_step = true;
protected override void insert_rows (Gtk.ListStore store) {
Gtk.TreeIter iter;
unowned GLib.PtrArray list = this.browser.get_data<GLib.PtrArray> ("history-list-tab-history-new");
store_append_row (list, store, out iter);
store_append_row (list, store);
if ((int)list.len == 0) {
this.old_tabs = true;
var label = new Gtk.Label (_("There are no unvisited tabs"));
this.vbox.pack_start (label, true, true, 0);
unowned GLib.PtrArray list_old = this.browser.get_data<GLib.PtrArray> ("history-list-tab-history");
store_append_row (list_old, store, out iter);
store_append_row (list_old, store);
}
}
@ -234,7 +276,7 @@ namespace HistoryList {
private class PreferencesDialog : Gtk.Dialog {
protected Manager hl_manager;
protected ComboBox closing_behavior;
protected Gtk.ComboBox closing_behavior;
public PreferencesDialog (Manager manager) {
this.hl_manager = manager;
@ -252,9 +294,9 @@ namespace HistoryList {
private void response_cb (Gtk.Dialog source, int response_id) {
switch (response_id) {
case ResponseType.APPLY:
case Gtk.ResponseType.APPLY:
int value;
TreeIter iter;
Gtk.TreeIter iter;
this.closing_behavior.get_active_iter (out iter);
var model = this.closing_behavior.get_model ();
@ -265,26 +307,26 @@ namespace HistoryList {
this.destroy ();
break;
case ResponseType.CANCEL:
case Gtk.ResponseType.CANCEL:
this.destroy ();
break;
}
}
private void create_widgets () {
ListStore model;
TreeIter iter;
TreeIter? active_iter = null;
Gtk.ListStore model;
Gtk.TreeIter iter;
Gtk.TreeIter? active_iter = null;
var table = new Table (1, 2, true);
var renderer = new CellRendererText ();
var table = new Gtk.Table (1, 2, true);
var renderer = new Gtk.CellRendererText ();
var label = new Label ( _("Tab closing behavior"));
var label = new Gtk.Label ( _("Tab closing behavior"));
table.attach_defaults (label, 0, 1, 0, 1);
var tab_closing_behavior = this.hl_manager.get_integer ("TabClosingBehavior");
model = new ListStore (2, typeof (string), typeof (int));
model = new Gtk.ListStore (2, typeof (string), typeof (int));
model.append (out iter);
model.set (iter, TabClosingBehaviorModel.TEXT, _("Do nothing"),
@ -304,7 +346,7 @@ namespace HistoryList {
if (TabClosingBehavior.NEW == tab_closing_behavior)
active_iter = iter;
this.closing_behavior = new ComboBox.with_model (model);
this.closing_behavior = new Gtk.ComboBox.with_model (model);
this.closing_behavior.set_active_iter (active_iter);
this.closing_behavior.pack_start (renderer, true);
this.closing_behavior.set_attributes (renderer, "text", 0);
@ -323,8 +365,8 @@ namespace HistoryList {
this.vbox.pack_start (table, false, true, 0);
#endif
this.add_button (Gtk.STOCK_CANCEL, ResponseType.CANCEL);
this.add_button (Gtk.STOCK_APPLY, ResponseType.APPLY);
this.add_button (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL);
this.add_button (Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY);
this.show_all ();
}
@ -334,6 +376,7 @@ namespace HistoryList {
public signal void preferences_changed ();
protected uint escKeyval;
protected uint delKeyval;
protected uint modifier_count;
protected int closing_behavior;
protected HistoryWindow? history_window;
@ -363,7 +406,7 @@ namespace HistoryList {
return false;
}
public bool key_release (Gdk.EventKey event_key, Browser browser) {
public bool key_release (Gdk.EventKey event_key, Midori.Browser browser) {
if (is_key_a_modifier (event_key)) {
this.modifier_count--;
}
@ -378,11 +421,13 @@ namespace HistoryList {
}
this.history_window.destroy ();
this.history_window = null;
} else if (event_key.keyval == this.delKeyval) {
this.history_window.close_tab ();
}
return false;
}
public void walk (Gtk.Action action, Browser browser, Type type, int step) {
public void walk (Gtk.Action action, Midori.Browser browser, Type type, int step) {
Midori.View? view = null;
view = browser.get_data<Midori.View?> ("history-list-last-change");
if (view != null) {
@ -417,7 +462,7 @@ namespace HistoryList {
hw.walk (step);
}
public void special_function (Gtk.Action action, Browser browser) {
public void special_function (Gtk.Action action, Midori.Browser browser) {
if (this.history_window != null) {
this.ignoreNextChange = true;
this.history_window.make_update ();
@ -578,6 +623,7 @@ namespace HistoryList {
foreach (var browser in app.get_browsers ())
browser_added (browser);
app.add_browser.connect (browser_added);
app.remove_browser.connect (browser_removed);
}
void deactivated () {
@ -585,6 +631,7 @@ namespace HistoryList {
foreach (var browser in app.get_browsers ())
browser_removed (browser);
app.add_browser.disconnect (browser_added);
app.remove_browser.disconnect (browser_removed);
}
void show_preferences () {
@ -607,6 +654,7 @@ namespace HistoryList {
}
construct {
this.escKeyval = Gdk.keyval_from_name ("Escape");
this.delKeyval = Gdk.keyval_from_name ("Delete");
}
}
}

View file

@ -128,8 +128,19 @@ struct MouseGestureNode
static guint
dist_sqr (guint x1, guint y1, guint x2, guint y2)
{
guint xdiff = abs(x1 - x2);
guint ydiff = abs(y1 - y2);
guint xdiff = 0, ydiff = 0;
// Remember that x1, x2, y1 and y2 are guint unsigned integers.
// Subtracting a greater number from a lower one is undefined.
// This guards against that.
if (x1 > x2)
xdiff = x1 - x2;
else
xdiff = x2 - x1;
if (y1 > y2)
ydiff = y1 - y2;
else
ydiff = y2 - y1;
return xdiff * xdiff + ydiff * ydiff;
}
@ -159,7 +170,7 @@ vector_follows_direction (float angle, float distance, MouseGestureDirection dir
return distance < MINLENGTH / 2;
float dir_angle = get_angle_for_direction (direction);
if (fabsf(angle - dir_angle) < DEVIANCE || fabsf(angle - dir_angle + 2 * M_PI) < DEVIANCE)
if (fabsf (angle - dir_angle) < DEVIANCE || fabsf (angle - dir_angle + 2 * (float)(M_PI)) < DEVIANCE)
return TRUE;
if(distance < MINLENGTH / 2)
@ -267,8 +278,8 @@ mouse_gestures_motion_notify_event_cb (GtkWidget* web_view,
if (distance >= MINLENGTH)
{
gesture->strokes[gesture->count] = nearest_direction_for_angle (angle);
if(midori_debug ("adblock:match"))
g_debug ("detected %s\n", direction_names[gesture->strokes[gesture->count]]);
if (midori_debug ("mouse"))
g_print ("mouse_gestures detected %s\n", direction_names[gesture->strokes[gesture->count]]);
}
}
else if (!vector_follows_direction (angle, distance, old_direction)
@ -384,7 +395,7 @@ mouse_gestures_load_config (MidoriExtension* extension)
for(i = 0; keys[i]; i++)
{
gsize n_strokes;
int j;
guint j;
gchar** stroke_strings = g_key_file_get_string_list (keyfile, "gestures", keys[i], &n_strokes,
NULL);

View file

@ -0,0 +1 @@
This is an extension for Midori web browser. With this extension you can manage which sites are allowed to execute javascript.

79
extensions/nojs/main.c Normal file
View file

@ -0,0 +1,79 @@
/*
Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
#include "nojs.h"
#include "nojs-preferences.h"
/* Global instance */
NoJS *noJS=NULL;
/* This extension was activated */
static void _nojs_on_activate(MidoriExtension *inExtension, MidoriApp *inApp, gpointer inUserData)
{
g_return_if_fail(noJS==NULL);
noJS=nojs_new(inExtension, inApp);
nojs_set_policy_for_unknown_domain(noJS, midori_extension_get_integer(inExtension, "unknown-domain-policy"));
nojs_set_allow_local_pages(noJS, midori_extension_get_boolean(inExtension, "allow-local-pages"));
nojs_set_only_second_level_domain(noJS, midori_extension_get_boolean(inExtension, "only-second-level"));
}
/* This extension was deactivated */
static void _nojs_on_deactivate(MidoriExtension *inExtension, gpointer inUserData)
{
g_return_if_fail(noJS);
g_object_unref(noJS);
noJS=NULL;
}
/* Preferences of this extension should be opened */
static void _nojs_on_preferences_response(GtkWidget* inDialog,
gint inResponse,
gpointer *inUserData)
{
gtk_widget_destroy(inDialog);
}
static void _nojs_on_open_preferences(MidoriExtension *inExtension)
{
g_return_if_fail(noJS);
/* Show preferences window */
GtkWidget* dialog;
dialog=nojs_preferences_new(noJS);
gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
g_signal_connect(dialog, "response", G_CALLBACK (_nojs_on_preferences_response), NULL);
gtk_widget_show_all(dialog);
}
/* Main entry for extension */
MidoriExtension *extension_init(void)
{
/* Set up extension */
MidoriExtension *extension=g_object_new(MIDORI_TYPE_EXTENSION,
"name", _("NoJS"),
"description", _("Manage javascript permission per site"),
"version", "0.1" MIDORI_VERSION_SUFFIX,
"authors", "Stephan Haller <nomad@froevel.de>",
NULL);
midori_extension_install_integer(extension, "unknown-domain-policy", NOJS_POLICY_BLOCK);
midori_extension_install_boolean(extension, "allow-local-pages", TRUE);
midori_extension_install_boolean(extension, "only-second-level", TRUE);
g_signal_connect(extension, "activate", G_CALLBACK(_nojs_on_activate), NULL);
g_signal_connect(extension, "deactivate", G_CALLBACK(_nojs_on_deactivate), NULL);
g_signal_connect(extension, "open-preferences", G_CALLBACK(_nojs_on_open_preferences), NULL);
return(extension);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,55 @@
/*
Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
#ifndef __NOJS_PREFERENCES__
#define __NOJS_PREFERENCES__
#include "config.h"
#include <midori/midori.h>
#include "nojs.h"
G_BEGIN_DECLS
#define TYPE_NOJS_PREFERENCES (nojs_preferences_get_type())
#define NOJS_PREFERENCES(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_NOJS_PREFERENCES, NoJSPreferences))
#define IS_NOJS_PREFERENCES(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_NOJS_PREFERENCES))
#define NOJS_PREFERENCES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_NOJS_PREFERENCES, NoJSPreferencesClass))
#define IS_NOJS_PREFERENCES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_NOJS_PREFERENCES))
#define NOJS_PREFERENCES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_NOJS_PREFERENCES, NoJSPreferencesClass))
typedef struct _NoJSPreferences NoJSPreferences;
typedef struct _NoJSPreferencesClass NoJSPreferencesClass;
typedef struct _NoJSPreferencesPrivate NoJSPreferencesPrivate;
struct _NoJSPreferences
{
/* Parent instance */
GtkDialog parent_instance;
/* Private structure */
NoJSPreferencesPrivate *priv;
};
struct _NoJSPreferencesClass
{
/* Parent class */
GtkDialogClass parent_class;
};
/* Public API */
GType nojs_preferences_get_type(void);
GtkWidget* nojs_preferences_new(NoJS *inManager);
G_END_DECLS
#endif /* __NOJS_PREFERENCES__ */

807
extensions/nojs/nojs-view.c Normal file
View file

@ -0,0 +1,807 @@
/*
Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
#include "nojs-view.h"
#include "nojs-preferences.h"
/* Define this class in GObject system */
G_DEFINE_TYPE(NoJSView,
nojs_view,
G_TYPE_OBJECT)
/* Properties */
enum
{
PROP_0,
PROP_MANAGER,
PROP_BROWSER,
PROP_VIEW,
PROP_MENU_ICON_STATE,
PROP_LAST
};
static GParamSpec* NoJSViewProperties[PROP_LAST]={ 0, };
/* Private structure - access only by public API if needed */
#define NOJS_VIEW_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE((obj), TYPE_NOJS_VIEW, NoJSViewPrivate))
struct _NoJSViewPrivate
{
/* Extension related */
NoJS *manager;
MidoriBrowser *browser;
MidoriView *view;
GtkWidget *menu;
gboolean menuPolicyWasChanged;
NoJSMenuIconState menuIconState;
GSList *resourceURIs;
};
/* IMPLEMENTATION: Private variables and methods */
/* Preferences of this extension should be opened */
static void _nojs_view_on_preferences_response(GtkWidget* inDialog,
gint inResponse,
gpointer *inUserData)
{
gtk_widget_destroy(inDialog);
}
static void _nojs_view_on_open_preferences(NoJSView *self, gpointer inUserData)
{
g_return_if_fail(NOJS_IS_VIEW(self));
NoJSViewPrivate *priv=self->priv;
/* Show preferences window */
GtkWidget* dialog;
dialog=nojs_preferences_new(priv->manager);
gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
g_signal_connect(dialog, "response", G_CALLBACK (_nojs_view_on_preferences_response), self);
gtk_widget_show_all(dialog);
}
/* Selection was done in menu */
static void _nojs_view_on_menu_selection_done(NoJSView *self, gpointer inUserData)
{
g_return_if_fail(NOJS_IS_VIEW(self));
NoJSViewPrivate *priv=self->priv;
/* Check if any policy was changed and reload page */
if(priv->menuPolicyWasChanged!=FALSE)
{
/* Reset flag that any policy was changed */
priv->menuPolicyWasChanged=FALSE;
/* Reload page */
midori_view_reload(priv->view, FALSE);
g_message("%s: Reloading page %s as policy has changed", __func__, midori_view_get_display_uri(priv->view));
}
}
/* Destroy menu */
static void _nojs_view_destroy_menu(NoJSView *self)
{
g_return_if_fail(NOJS_IS_VIEW(self));
g_return_if_fail(self->priv->menu!=NULL);
NoJSViewPrivate *priv=self->priv;
/* Empty menu and list of domains added to menu */
gtk_widget_destroy(priv->menu);
priv->menu=NULL;
/* Reset menu icon to default state */
priv->menuIconState=NOJS_MENU_ICON_STATE_UNDETERMINED;
g_object_notify_by_pspec(G_OBJECT(self), NoJSViewProperties[PROP_MENU_ICON_STATE]);
}
/* Create empty menu */
static void _nojs_view_create_empty_menu(NoJSView *self)
{
g_return_if_fail(NOJS_IS_VIEW(self));
g_return_if_fail(self->priv->menu==NULL);
NoJSViewPrivate *priv=self->priv;
GtkWidget *item;
/* Create new menu and set up default items */
priv->menu=gtk_menu_new();
item=gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, NULL);
g_signal_connect_swapped(item, "activate", G_CALLBACK(_nojs_view_on_open_preferences), self);
gtk_menu_shell_prepend(GTK_MENU_SHELL(priv->menu), item);
gtk_widget_show_all(item);
/* Reset flag that any policy was changed */
priv->menuPolicyWasChanged=FALSE;
/* Reset menu icon to default state */
priv->menuIconState=NOJS_MENU_ICON_STATE_UNDETERMINED;
g_object_notify_by_pspec(G_OBJECT(self), NoJSViewProperties[PROP_MENU_ICON_STATE]);
/* Connect signal to menu */
g_signal_connect_swapped(priv->menu, "selection-done", G_CALLBACK(_nojs_view_on_menu_selection_done), self);
}
/* Change visibility state of menu item for a domain depending on policy */
static gboolean _nojs_view_menu_item_change_policy(NoJSView *self, const gchar *inDomain, NoJSPolicy inPolicy)
{
g_return_val_if_fail(NOJS_IS_VIEW(self), FALSE);
g_return_val_if_fail(inDomain, FALSE);
NoJSViewPrivate *priv=self->priv;
GList *items, *iter;
gboolean updated;
/* Handle accept-for-session like accept when showing or hiding menu items */
if(inPolicy==NOJS_POLICY_ACCEPT_TEMPORARILY) inPolicy=NOJS_POLICY_ACCEPT;
/* Update menu items */
updated=FALSE;
items=gtk_container_get_children(GTK_CONTAINER(priv->menu));
for(iter=items; iter; iter=iter->next)
{
/* Only check and update menu items (not separators and so on) */
if(GTK_IS_MENU_ITEM(iter->data))
{
GtkMenuItem *item=GTK_MENU_ITEM(iter->data);
const gchar *itemDomain;
NoJSPolicy itemPolicy;
itemDomain=(const gchar*)g_object_get_data(G_OBJECT(item), "domain");
itemPolicy=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "policy"));
/* Handle accept-for-session like accept when showing or hiding menu items */
if(itemPolicy==NOJS_POLICY_ACCEPT_TEMPORARILY) itemPolicy=NOJS_POLICY_ACCEPT;
/* If menu item has "domain"-data update its visibility state
* depending on matching policy
*/
if(g_strcmp0(itemDomain, inDomain)==0)
{
if(itemPolicy==inPolicy) gtk_widget_hide(GTK_WIDGET(item));
else gtk_widget_show_all(GTK_WIDGET(item));
/* Set flag that at least one menu item was updated */
updated=TRUE;
}
}
}
g_list_free(items);
/* Return flag indicating if at least one menu item was updated */
return(updated);
}
/* A menu item was selected */
static void _nojs_view_on_menu_item_activate(NoJSView *self, gpointer inUserData)
{
g_return_if_fail(NOJS_IS_VIEW(self));
g_return_if_fail(GTK_IS_MENU_ITEM(inUserData));
NoJSViewPrivate *priv=self->priv;
GtkMenuItem *item=GTK_MENU_ITEM(inUserData);
const gchar *domain;
NoJSPolicy policy;
/* Get domain and policy to set */
domain=(const gchar*)g_object_get_data(G_OBJECT(item), "domain");
policy=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "policy"));
g_return_if_fail(domain);
g_return_if_fail(policy>=NOJS_POLICY_ACCEPT && policy<=NOJS_POLICY_BLOCK);
/* Set policy for domain and update menu items */
_nojs_view_menu_item_change_policy(self, domain, policy);
nojs_set_policy(priv->manager, domain, policy);
/* Set flag that a policy was changed */
priv->menuPolicyWasChanged=TRUE;
}
/* Add site to menu */
static void _nojs_view_add_site_to_menu(NoJSView *self, const gchar *inDomain, NoJSPolicy inPolicy)
{
g_return_if_fail(NOJS_IS_VIEW(self));
g_return_if_fail(inDomain);
NoJSViewPrivate *priv=self->priv;
GtkWidget *item;
gchar *itemLabel;
GtkWidget *itemImage;
static gint INSERT_POSITION=1;
NoJSMenuIconState newMenuIconState;
/* Create menu object if not available */
if(!priv->menu) _nojs_view_create_empty_menu(self);
/* Check if domain was already added to menu. If it exists just update it. */
if(_nojs_view_menu_item_change_policy(self, inDomain, inPolicy)==TRUE) return;
/* Add menu item(s) for domain */
itemLabel=g_strdup_printf(_("Deny %s"), inDomain);
item=gtk_image_menu_item_new_with_label(itemLabel);
itemImage=gtk_image_new_from_stock (GTK_STOCK_NO, GTK_ICON_SIZE_MENU);
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), itemImage);
gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(item), TRUE);
gtk_menu_shell_insert(GTK_MENU_SHELL(priv->menu), item, INSERT_POSITION);
if(inPolicy!=NOJS_POLICY_BLOCK) gtk_widget_show_all(item);
g_object_set_data_full(G_OBJECT(item), "domain", g_strdup(inDomain), (GDestroyNotify)g_free);
g_object_set_data(G_OBJECT(item), "policy", GINT_TO_POINTER(NOJS_POLICY_BLOCK));
g_signal_connect_swapped(item, "activate", G_CALLBACK(_nojs_view_on_menu_item_activate), self);
g_free(itemLabel);
itemLabel=g_strdup_printf(_("Allow %s"), inDomain);
item=gtk_image_menu_item_new_with_label(itemLabel);
itemImage=gtk_image_new_from_stock (GTK_STOCK_YES, GTK_ICON_SIZE_MENU);
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), itemImage);
gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(item), TRUE);
gtk_menu_shell_insert(GTK_MENU_SHELL(priv->menu), item, INSERT_POSITION);
if(inPolicy!=NOJS_POLICY_ACCEPT && inPolicy!=NOJS_POLICY_ACCEPT_TEMPORARILY) gtk_widget_show_all(item);
g_object_set_data_full(G_OBJECT(item), "domain", g_strdup(inDomain), (GDestroyNotify)g_free);
g_object_set_data(G_OBJECT(item), "policy", GINT_TO_POINTER(NOJS_POLICY_ACCEPT));
g_signal_connect_swapped(item, "activate", G_CALLBACK(_nojs_view_on_menu_item_activate), self);
g_free(itemLabel);
itemLabel=g_strdup_printf(_("Allow %s this session"), inDomain);
item=gtk_image_menu_item_new_with_label(itemLabel);
itemImage=gtk_image_new_from_stock (GTK_STOCK_OK, GTK_ICON_SIZE_MENU);
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), itemImage);
gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(item), TRUE);
gtk_menu_shell_insert(GTK_MENU_SHELL(priv->menu), item, INSERT_POSITION);
if(inPolicy!=NOJS_POLICY_ACCEPT && inPolicy!=NOJS_POLICY_ACCEPT_TEMPORARILY) gtk_widget_show_all(item);
g_object_set_data_full(G_OBJECT(item), "domain", g_strdup(inDomain), (GDestroyNotify)g_free);
g_object_set_data(G_OBJECT(item), "policy", GINT_TO_POINTER(NOJS_POLICY_ACCEPT_TEMPORARILY));
g_signal_connect_swapped(item, "activate", G_CALLBACK(_nojs_view_on_menu_item_activate), self);
g_free(itemLabel);
/* Add seperator to seperate actions for this domain from the other domains */
item=gtk_separator_menu_item_new();
gtk_menu_shell_insert(GTK_MENU_SHELL(priv->menu), item, INSERT_POSITION);
gtk_widget_show_all(item);
/* Determine state of status icon */
if(priv->menuIconState!=NOJS_MENU_ICON_STATE_MIXED)
{
switch(inPolicy)
{
case NOJS_POLICY_ACCEPT:
case NOJS_POLICY_ACCEPT_TEMPORARILY:
newMenuIconState=NOJS_MENU_ICON_STATE_ALLOWED;
break;
case NOJS_POLICY_BLOCK:
newMenuIconState=NOJS_MENU_ICON_STATE_DENIED;
break;
default:
newMenuIconState=NOJS_MENU_ICON_STATE_MIXED;
break;
}
if(priv->menuIconState==NOJS_MENU_ICON_STATE_UNDETERMINED ||
priv->menuIconState!=newMenuIconState)
{
priv->menuIconState=newMenuIconState;
g_object_notify_by_pspec(G_OBJECT(self), NoJSViewProperties[PROP_MENU_ICON_STATE]);
}
}
}
/* Status of loading a site has changed */
static void _nojs_view_on_load_status_changed(NoJSView *self, GParamSpec *inSpec, gpointer inUserData)
{
g_return_if_fail(NOJS_IS_VIEW(self));
g_return_if_fail(WEBKIT_IS_WEB_VIEW(inUserData));
NoJSViewPrivate *priv=self->priv;
WebKitWebView *webkitView=WEBKIT_WEB_VIEW(inUserData);
WebKitWebSettings *settings=webkit_web_view_get_settings(webkitView);
WebKitLoadStatus status;
SoupURI *uri;
/* Get URI of document loading/loaded */
uri=soup_uri_new(webkit_web_view_get_uri(webkitView));
/* Check load status */
status=webkit_web_view_get_load_status(webkitView);
/* Check if a view was emptied, e.g. for a new document going to be loaded soon */
if(status==WEBKIT_LOAD_PROVISIONAL)
{
/* Create a new empty menu */
_nojs_view_destroy_menu(self);
_nojs_view_create_empty_menu(self);
/* Free list of resource URIs, that's the list of URIs for all resources
* of a page
*/
if(priv->resourceURIs)
{
g_slist_free_full(priv->resourceURIs, (GDestroyNotify)g_free);
priv->resourceURIs=NULL;
}
}
/* Check if document loading is going to start. Do not check special pages. */
if(status==WEBKIT_LOAD_COMMITTED &&
uri &&
uri->scheme &&
g_strcmp0(uri->scheme, "about")!=0)
{
/* Check if domain is black-listed or white-listed and enable or
* disable javascript accordingly. But if settings match already
* the state it should get do not set it again to avoid reloads of page.
*/
gchar *domain;
NoJSPolicy policy;
gboolean currentScriptsEnabled;
gboolean newScriptsEnabled;
domain=nojs_get_domain(priv->manager, uri);
policy=nojs_get_policy(priv->manager, uri);
if(policy==NOJS_POLICY_UNDETERMINED)
{
policy=nojs_get_policy_for_unknown_domain(priv->manager);
// TODO: Show nick_name of policy (enum) to use in warning
g_warning("Got invalid policy. Using default policy for unknown domains.");
}
newScriptsEnabled=(policy==NOJS_POLICY_BLOCK ? FALSE : TRUE);
g_object_get(G_OBJECT(settings), "enable-scripts", &currentScriptsEnabled, NULL);
if(newScriptsEnabled!=currentScriptsEnabled)
{
g_object_set(G_OBJECT(settings), "enable-scripts", newScriptsEnabled, NULL);
// TODO: Set uri also to ensure this uri is going to be reloaded
}
if(domain)
{
_nojs_view_add_site_to_menu(self, domain, policy);
g_free(domain);
}
}
/* Free allocated resources */
if(uri) soup_uri_free(uri);
}
/* A request is going to sent */
static void _nojs_view_on_resource_request_starting(NoJSView *self,
WebKitWebFrame *inFrame,
WebKitWebResource *inResource,
WebKitNetworkRequest *inRequest,
WebKitNetworkResponse *inResponse,
gpointer inUserData)
{
g_return_if_fail(NOJS_IS_VIEW(self));
NoJSViewPrivate *priv=self->priv;
SoupMessage *message;
SoupURI *uri;
gchar *uriText;
/* Remember resource URIs requesting */
message=(inRequest ? webkit_network_request_get_message(inRequest) : NULL);
if(message)
{
uri=soup_message_get_uri(message);
if(uri)
{
uriText=soup_uri_to_string(uri, FALSE);
priv->resourceURIs=g_slist_prepend(priv->resourceURIs, uriText);
}
}
message=(inResponse ? webkit_network_response_get_message(inResponse) : NULL);
if(message)
{
uri=soup_message_get_uri(message);
if(uri)
{
uriText=soup_uri_to_string(uri, FALSE);
priv->resourceURIs=g_slist_prepend(priv->resourceURIs, uriText);
}
}
}
/* A policy has changed */
static void _nojs_view_on_policy_changed(NoJSView *self, gchar *inDomain, gpointer inUserData)
{
g_return_if_fail(NOJS_IS_VIEW(self));
g_return_if_fail(inDomain);
NoJSViewPrivate *priv=self->priv;
GList *items, *iter;
gboolean reloaded;
/* Check if the policy of a domain has changed this view has referenced resources to */
reloaded=FALSE;
items=gtk_container_get_children(GTK_CONTAINER(priv->menu));
for(iter=items; reloaded==FALSE && iter; iter=iter->next)
{
if(GTK_IS_MENU_ITEM(iter->data))
{
const gchar *itemDomain;
/* Check if domain matches menu item */
itemDomain=(const gchar*)g_object_get_data(G_OBJECT(iter->data), "domain");
if(g_strcmp0(itemDomain, inDomain)==0)
{
/* Found domain in our menu so reload page */
midori_view_reload(priv->view, FALSE);
reloaded=TRUE;
}
}
}
g_list_free(items);
}
/* A javascript URI is going to loaded or blocked */
static void _nojs_view_on_uri_load_policy_status(NoJSView *self, gchar *inURI, NoJSPolicy inPolicy, gpointer inUserData)
{
g_return_if_fail(NOJS_IS_VIEW(self));
NoJSViewPrivate *priv=self->priv;
GSList *iter;
gchar *checkURI;
/* Check if uri (accepted or blocked) might be one of ours */
for(iter=priv->resourceURIs; iter; iter=iter->next)
{
checkURI=(gchar*)iter->data;
if(g_strcmp0(checkURI, inURI)==0)
{
SoupURI *uri;
gchar *domain;
uri=soup_uri_new(inURI);
domain=nojs_get_domain(priv->manager, uri);
if(domain)
{
_nojs_view_add_site_to_menu(self, domain, inPolicy);
g_free(domain);
}
soup_uri_free(uri);
break;
}
}
}
/* Property "view" has changed */
static void _nojs_view_on_view_changed(NoJSView *self, MidoriView *inView)
{
NoJSViewPrivate *priv=self->priv;
WebKitWebView *webkitView;
/* Disconnect signal on old view */
if(priv->view)
{
webkitView=WEBKIT_WEB_VIEW(midori_view_get_web_view(priv->view));
g_signal_handlers_disconnect_by_data(webkitView, self);
g_object_set_data(G_OBJECT(priv->view), "nojs-view-instance", NULL);
g_object_unref(priv->view);
priv->view=NULL;
}
/* Set new view if valid pointer */
if(!inView) return;
priv->view=g_object_ref(inView);
g_object_set_data(G_OBJECT(priv->view), "nojs-view-instance", self);
/* Listen to changes of load-status in view */
webkitView=WEBKIT_WEB_VIEW(midori_view_get_web_view(priv->view));
g_signal_connect_swapped(webkitView, "notify::load-status", G_CALLBACK(_nojs_view_on_load_status_changed), self);
g_signal_connect_swapped(webkitView, "resource-request-starting", G_CALLBACK(_nojs_view_on_resource_request_starting), self);
/* Create empty menu */
_nojs_view_destroy_menu(self);
_nojs_view_create_empty_menu(self);
/* Release list of resource URIs */
if(priv->resourceURIs)
{
g_slist_free_full(priv->resourceURIs, (GDestroyNotify)g_free);
priv->resourceURIs=NULL;
}
}
/* This extension is going to be deactivated */
static void _nojs_view_on_extension_deactivated(NoJSView *self, MidoriExtension *inExtension)
{
g_return_if_fail(NOJS_IS_VIEW(self));
/* Dispose allocated resources by unreferencing ourselve */
g_object_unref(self);
}
/* Property "manager" has changed */
static void _nojs_view_on_manager_changed(NoJSView *self, NoJS *inNoJS)
{
g_return_if_fail(NOJS_IS_VIEW(self));
g_return_if_fail(!inNoJS || IS_NOJS(inNoJS));
NoJSViewPrivate *priv=self->priv;
MidoriExtension *extension;
/* Release reference to old manager and clean up */
if(priv->manager)
{
g_object_get(priv->manager, "extension", &extension, NULL);
g_signal_handlers_disconnect_by_data(extension, self);
g_object_unref(extension);
g_signal_handlers_disconnect_by_data(priv->manager, self);
g_object_unref(priv->manager);
priv->manager=NULL;
}
/* Set new view if valid pointer */
if(!inNoJS) return;
priv->manager=g_object_ref(inNoJS);
/* Connect signals to manager */
g_signal_connect_swapped(priv->manager, "uri-load-policy-status", G_CALLBACK(_nojs_view_on_uri_load_policy_status), self);
g_signal_connect_swapped(priv->manager, "policy-changed", G_CALLBACK(_nojs_view_on_policy_changed), self);
/* Connect signal to get noticed when extension is going to be deactivated
* to release all references to GObjects
*/
g_object_get(priv->manager, "extension", &extension, NULL);
g_signal_connect_swapped(extension, "deactivate", G_CALLBACK(_nojs_view_on_extension_deactivated), self);
g_object_unref(extension);
}
/* IMPLEMENTATION: GObject */
/* Finalize this object */
static void nojs_view_finalize(GObject *inObject)
{
NoJSView *self=NOJS_VIEW(inObject);
NoJSViewPrivate *priv=self->priv;
/* Dispose allocated resources */
if(priv->manager)
{
MidoriExtension *extension;
g_object_get(priv->manager, "extension", &extension, NULL);
g_signal_handlers_disconnect_by_data(extension, self);
g_object_unref(extension);
g_signal_handlers_disconnect_by_data(priv->manager, self);
g_object_unref(priv->manager);
priv->manager=NULL;
}
if(priv->browser)
{
g_object_unref(priv->browser);
priv->browser=NULL;
}
if(priv->view)
{
_nojs_view_on_view_changed(self, NULL);
}
if(priv->menu)
{
gtk_widget_destroy(priv->menu);
priv->menu=NULL;
}
if(priv->resourceURIs)
{
g_slist_free_full(priv->resourceURIs, (GDestroyNotify)g_free);
priv->resourceURIs=NULL;
}
/* Call parent's class finalize method */
G_OBJECT_CLASS(nojs_view_parent_class)->finalize(inObject);
}
/* Set/get properties */
static void nojs_view_set_property(GObject *inObject,
guint inPropID,
const GValue *inValue,
GParamSpec *inSpec)
{
NoJSView *self=NOJS_VIEW(inObject);
switch(inPropID)
{
/* Construct-only properties */
case PROP_MANAGER:
_nojs_view_on_manager_changed(self, NOJS(g_value_get_object(inValue)));
break;
case PROP_BROWSER:
if(self->priv->browser) g_object_unref(self->priv->browser);
self->priv->browser=g_object_ref(g_value_get_object(inValue));
break;
case PROP_VIEW:
_nojs_view_on_view_changed(self, MIDORI_VIEW(g_value_get_object(inValue)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
break;
}
}
static void nojs_view_get_property(GObject *inObject,
guint inPropID,
GValue *outValue,
GParamSpec *inSpec)
{
NoJSView *self=NOJS_VIEW(inObject);
switch(inPropID)
{
case PROP_MANAGER:
g_value_set_object(outValue, self->priv->manager);
break;
case PROP_BROWSER:
g_value_set_object(outValue, self->priv->browser);
break;
case PROP_VIEW:
g_value_set_object(outValue, self->priv->view);
break;
case PROP_MENU_ICON_STATE:
g_value_set_enum(outValue, self->priv->menuIconState);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
break;
}
}
/* Class initialization
* Override functions in parent classes and define properties and signals
*/
static void nojs_view_class_init(NoJSViewClass *klass)
{
GObjectClass *gobjectClass=G_OBJECT_CLASS(klass);
/* Override functions */
gobjectClass->finalize=nojs_view_finalize;
gobjectClass->set_property=nojs_view_set_property;
gobjectClass->get_property=nojs_view_get_property;
/* Set up private structure */
g_type_class_add_private(klass, sizeof(NoJSViewPrivate));
/* Define properties */
NoJSViewProperties[PROP_MANAGER]=
g_param_spec_object("manager",
_("Manager instance"),
_("Instance to global NoJS manager"),
TYPE_NOJS,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
NoJSViewProperties[PROP_BROWSER]=
g_param_spec_object("browser",
_("Browser window"),
_("The Midori browser instance this view belongs to"),
MIDORI_TYPE_BROWSER,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
NoJSViewProperties[PROP_VIEW]=
g_param_spec_object("view",
_("View"),
_("The Midori view instance this view belongs to"),
MIDORI_TYPE_VIEW,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
NoJSViewProperties[PROP_MENU_ICON_STATE]=
g_param_spec_enum("menu-icon-state",
_("Menu icon state"),
_("State of menu icon to show in status bar"),
NOJS_TYPE_MENU_ICON_STATE,
NOJS_MENU_ICON_STATE_UNDETERMINED,
G_PARAM_READABLE);
g_object_class_install_properties(gobjectClass, PROP_LAST, NoJSViewProperties);
}
/* Object initialization
* Create private structure and set up default values
*/
static void nojs_view_init(NoJSView *self)
{
NoJSViewPrivate *priv;
priv=self->priv=NOJS_VIEW_GET_PRIVATE(self);
/* Set up default values */
priv->manager=NULL;
priv->browser=NULL;
priv->view=NULL;
priv->menu=NULL;
priv->menuPolicyWasChanged=FALSE;
priv->menuIconState=NOJS_MENU_ICON_STATE_UNDETERMINED;
priv->resourceURIs=NULL;
/* Create empty menu */
_nojs_view_create_empty_menu(self);
}
/* Implementation: Public API */
/* Create new object */
NoJSView* nojs_view_new(NoJS *inNoJS, MidoriBrowser *inBrowser, MidoriView *inView)
{
return(g_object_new(TYPE_NOJS_VIEW,
"manager", inNoJS,
"browser", inBrowser,
"view", inView,
NULL));
}
/* Get menu widget for this view */
GtkMenu* nojs_view_get_menu(NoJSView *self)
{
g_return_val_if_fail(NOJS_IS_VIEW(self), NULL);
return(GTK_MENU(self->priv->menu));
}
/* Get image used for menu icon in status bar */
NoJSMenuIconState nojs_view_get_menu_icon_state(NoJSView *self)
{
g_return_val_if_fail(NOJS_IS_VIEW(self), NOJS_MENU_ICON_STATE_UNDETERMINED);
return(self->priv->menuIconState);
}
/************************************************************************************/
/* Implementation: Enumeration */
GType nojs_menu_icon_state_get_type(void)
{
static volatile gsize g_define_type_id__volatile=0;
if(g_once_init_enter(&g_define_type_id__volatile))
{
static const GEnumValue values[]=
{
{ NOJS_MENU_ICON_STATE_UNDETERMINED, "NOJS_MENU_ICON_STATE_UNDETERMINED", N_("Undetermined") },
{ NOJS_MENU_ICON_STATE_ALLOWED, "NOJS_MENU_ICON_STATE_ALLOWED", N_("Allowed") },
{ NOJS_MENU_ICON_STATE_MIXED, "NOJS_MENU_ICON_STATE_MIXED", N_("Mixed") },
{ NOJS_MENU_ICON_STATE_DENIED, "NOJS_MENU_ICON_STATE_DENIED", N_("Denied") },
{ 0, NULL, NULL }
};
GType g_define_type_id=g_enum_register_static(g_intern_static_string("NoJSMenuIconState"), values);
g_once_init_leave(&g_define_type_id__volatile, g_define_type_id);
}
return(g_define_type_id__volatile);
}

View file

@ -0,0 +1,71 @@
/*
Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
#ifndef __NOJS_VIEW__
#define __NOJS_VIEW__
#include "config.h"
#include "nojs.h"
#include <midori/midori.h>
G_BEGIN_DECLS
/* NoJS view enums */
typedef enum
{
NOJS_MENU_ICON_STATE_UNDETERMINED,
NOJS_MENU_ICON_STATE_ALLOWED,
NOJS_MENU_ICON_STATE_MIXED,
NOJS_MENU_ICON_STATE_DENIED
} NoJSMenuIconState;
/* NoJS view object */
#define TYPE_NOJS_VIEW (nojs_view_get_type())
#define NOJS_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_NOJS_VIEW, NoJSView))
#define NOJS_IS_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_NOJS_VIEW))
#define NOJS_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_NOJS_VIEW, NoJSViewClass))
#define NOJS_IS_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_NOJS_VIEW))
#define NOJS_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_NOJS_VIEW, NoJSViewClass))
typedef struct _NoJSView NoJSView;
typedef struct _NoJSViewClass NoJSViewClass;
typedef struct _NoJSViewPrivate NoJSViewPrivate;
struct _NoJSView
{
/* Parent instance */
GObject parent_instance;
/* Private structure */
NoJSViewPrivate *priv;
};
struct _NoJSViewClass
{
/* Parent class */
GObjectClass parent_class;
};
/* Public API */
GType nojs_view_get_type(void);
NoJSView* nojs_view_new(NoJS *inNoJS, MidoriBrowser *inBrowser, MidoriView *inView);
GtkMenu* nojs_view_get_menu(NoJSView *self);
NoJSMenuIconState nojs_view_get_menu_icon_state(NoJSView *self);
/* Enumeration */
GType nojs_menu_icon_state_get_type(void) G_GNUC_CONST;
#define NOJS_TYPE_MENU_ICON_STATE (nojs_menu_icon_state_get_type())
G_END_DECLS
#endif /* __NOJS_VIEW__ */

1044
extensions/nojs/nojs.c Normal file

File diff suppressed because it is too large Load diff

89
extensions/nojs/nojs.h Normal file
View file

@ -0,0 +1,89 @@
/*
Copyright (C) 2013 Stephan Haller <nomad@froevel.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
#ifndef __NOJS__
#define __NOJS__
#include "config.h"
#include <midori/midori.h>
#define NOJS_DATABASE "nojs.db"
G_BEGIN_DECLS
/* NoJS manager enums */
typedef enum
{
NOJS_POLICY_UNDETERMINED,
NOJS_POLICY_ACCEPT,
NOJS_POLICY_ACCEPT_TEMPORARILY,
NOJS_POLICY_BLOCK
} NoJSPolicy;
/* NoJS manager object */
#define TYPE_NOJS (nojs_get_type())
#define NOJS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_NOJS, NoJS))
#define IS_NOJS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_NOJS))
#define NOJS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_NOJS, NoJSClass))
#define IS_NOJS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_NOJS))
#define NOJS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_NOJS, NoJSClass))
typedef struct _NoJS NoJS;
typedef struct _NoJSClass NoJSClass;
typedef struct _NoJSPrivate NoJSPrivate;
struct _NoJS
{
/* Parent instance */
GObject parent_instance;
/* Private structure */
NoJSPrivate *priv;
};
struct _NoJSClass
{
/* Parent class */
GObjectClass parent_class;
/* Virtual functions */
void (*uri_load_policy_status)(NoJS *self, gchar *inURI, NoJSPolicy inPolicy);
void (*policy_changed)(NoJS *self, gchar *inDomain);
};
/* Public API */
GType nojs_get_type(void);
NoJS* nojs_new(MidoriExtension *inExtension, MidoriApp *inApp);
gchar* nojs_get_domain(NoJS *self, SoupURI *inURI);
gint nojs_get_policy(NoJS *self, SoupURI *inURI);
void nojs_set_policy(NoJS *self, const gchar *inDomain, NoJSPolicy inPolicy);
NoJSPolicy nojs_get_policy_for_unknown_domain(NoJS *self);
void nojs_set_policy_for_unknown_domain(NoJS *self, NoJSPolicy inPolicy);
gboolean nojs_get_allow_local_pages(NoJS *self);
void nojs_set_allow_local_pages(NoJS *self, gboolean inAllow);
gboolean nojs_get_only_second_level_domain(NoJS *self);
void nojs_set_only_second_level_domain(NoJS *self, gboolean inOnlySecondLevel);
gchar* nojs_get_icon_path (const gchar* icon);
/* Enumeration */
GType nojs_policy_get_type(void) G_GNUC_CONST;
#define NOJS_TYPE_POLICY (nojs_policy_get_type())
G_END_DECLS
#endif /* __NOJS__ */

459
extensions/notes.vala Normal file
View file

@ -0,0 +1,459 @@
/*
Copyright (C) 2013 Paweł Forysiuk <tuxator@o2.pl>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
namespace ClipNotes {
Midori.Database database;
unowned Sqlite.Database db;
Gtk.ListStore notes_list_store;
Note? current_note;
class Note : GLib.Object {
public int64 id { get; set; }
public string title { get; set; }
public string? uri { get; set; default = null; }
public string content { get; set; default = ""; }
public void add (string title, string? uri, string note_content)
{
GLib.DateTime time = new DateTime.now_local ();
string sqlcmd = "INSERT INTO `notes` (`uri`, `title`, `note_content`, `tstamp` ) VALUES (:uri, :title, :note_content, :tstamp);";
Midori.DatabaseStatement statement;
try {
statement = database.prepare (sqlcmd,
":uri", typeof (string), uri,
":title", typeof (string), title,
":note_content", typeof (string), note_content,
":tstamp", typeof (int64), time.to_unix ());
statement.step ();
append_note (this);
} catch (Error error) {
critical (_("Failed to add new note to database: %s\n"), error.message);
}
this.id = db.last_insert_rowid ();
this.uri = uri;
this.title = title;
this.content = note_content;
}
public void remove ()
{
string sqlcmd = "DELETE FROM `notes` WHERE id= :id;";
Midori.DatabaseStatement statement;
try {
statement = database.prepare (sqlcmd,
":id", typeof (int64), this.id);
statement.step ();
remove_note (this.id);
} catch (Error error) {
critical (_("Falied to remove note from database: %s\n"), error.message);
}
}
public void rename (string new_title)
{
string sqlcmd = "UPDATE `notes` SET title= :title WHERE id = :id;";
Midori.DatabaseStatement statement;
try {
statement = database.prepare (sqlcmd,
":id", typeof (int64), this.id,
":title", typeof (string), new_title);
statement.step ();
} catch (Error error) {
critical (_("Falied to rename note: %s\n"), error.message);
}
this.title = new_title;
}
public void update (string new_content)
{
string sqlcmd = "UPDATE `notes` SET note_content= :content WHERE id = :id;";
Midori.DatabaseStatement statement;
try {
statement = database.prepare (sqlcmd,
":id", typeof (int64), this.id,
":content", typeof (string), new_content);
statement.step ();
} catch (Error error) {
critical (_("Falied to update note: %s\n"), error.message);
}
this.content = new_content;
}
}
void append_note (Note note)
{
/* Strip LRE leading character */
if (note.title != null && note.title.has_prefix (""))
note.title = note.title.replace ("", "");
Gtk.TreeIter iter;
notes_list_store.append (out iter);
notes_list_store.set (iter, 0, note);
}
void remove_note (int64 id)
{
Gtk.TreeIter iter;
if (notes_list_store.iter_children (out iter, null)) {
do {
Note note;
notes_list_store.get (iter, 0, out note);
if (id == note.id) {
if (current_note == note) {
current_note = null;
}
notes_list_store.remove (iter);
break;
}
} while (notes_list_store.iter_next (ref iter));
}
}
private class Sidebar : Gtk.VBox, Midori.Viewable {
Gtk.Toolbar? toolbar = null;
Gtk.Label note_label;
Gtk.TreeView notes_tree_view;
Gtk.TextView note_text_view = new Gtk.TextView ();
public unowned string get_stock_id () {
return Gtk.STOCK_EDIT;
}
public unowned string get_label () {
return _("Notes");
}
public Gtk.Widget get_toolbar () {
if (toolbar == null) {
toolbar = new Gtk.Toolbar ();
var new_note_button = new Gtk.ToolButton.from_stock (Gtk.STOCK_EDIT);
new_note_button.label = _("New Note");
new_note_button.tooltip_text = _("Creates a new empty note, unrelated to opened pages");
new_note_button.use_underline = true;
new_note_button.is_important = true;
new_note_button.show ();
new_note_button.clicked.connect (() => {
var note = new Note ();
note.add (_("New note"), null, "");
});
toolbar.insert (new_note_button, -1);
}
return toolbar;
}
internal void title_edited (Gtk.CellRendererText renderer, string? path_str, string? new_title) {
var path = new Gtk.TreePath.from_string (path_str);
Gtk.TreeIter iter;
notes_list_store.get_iter (out iter, path);
Note note;
notes_list_store.get (iter, 0, out note);
note.rename (new_title);
notes_list_store.set (iter, 0, note);
}
public Sidebar () {
Gtk.TreeViewColumn column;
notes_list_store = new Gtk.ListStore (1, typeof (Note));
notes_tree_view = new Gtk.TreeView.with_model (notes_list_store);
notes_tree_view.headers_visible = true;
notes_tree_view.button_press_event.connect (button_pressed);
notes_tree_view.get_selection().changed.connect (selection_changed);
notes_list_store.set_sort_column_id (0, Gtk.SortType.ASCENDING);
notes_list_store.set_sort_func (0, tree_sort_func);
column = new Gtk.TreeViewColumn ();
Gtk.CellRendererPixbuf renderer_icon = new Gtk.CellRendererPixbuf ();
column.pack_start (renderer_icon, false);
column.set_cell_data_func (renderer_icon, on_render_icon);
notes_tree_view.append_column (column);
column = new Gtk.TreeViewColumn ();
Gtk.CellRendererText renderer_title = new Gtk.CellRendererText ();
renderer_title.editable = true;
renderer_title.edited.connect (title_edited);
column.set_title (_("Notes"));
column.pack_start (renderer_title, true);
column.set_cell_data_func (renderer_title, on_render_note_title);
notes_tree_view.append_column (column);
try {
string sqlcmd = "SELECT id, uri, title, note_content FROM notes";
var statement = database.prepare (sqlcmd);
while (statement.step ()) {
var note = new Note ();
note.id = statement.get_int64 ("id");
note.uri = statement.get_string ("uri");
note.title = statement.get_string ("title");
note.content = statement.get_string ("note_content");
append_note (note);
}
} catch (Error error) {
critical (_("Failed to select from notes database: %s\n"), error.message);
}
notes_tree_view.show ();
pack_start (notes_tree_view, false, false, 0);
note_label = new Gtk.Label (null);
note_label.show ();
pack_start (note_label, false, false, 0);
note_text_view.set_wrap_mode (Gtk.WrapMode.WORD);
note_text_view.show ();
note_text_view.focus_out_event.connect (focus_lost);
pack_start (note_text_view, true, true, 0);
}
int tree_sort_func (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b) {
Note note1, note2;
model.get (a, 0, out note1);
model.get (b, 0, out note2);
return strcmp (note1.title, note2.title);
}
void save_current_note () {
if (current_note != null) {
string note_content = note_text_view.buffer.text;
if (note_content != current_note.content)
current_note.update (note_content);
}
}
bool focus_lost (Gdk.EventFocus event) {
save_current_note ();
return false;
}
private void on_render_note_title (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
Note note;
model.get (iter, 0, out note);
renderer.set ("markup", GLib.Markup.printf_escaped ("%s", note.title),
"ellipsize", Pango.EllipsizeMode.END);
}
private void on_render_icon (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
Note note;
model.get (iter, 0, out note);
var pixbuf = Midori.Paths.get_icon (note.uri, null);
if (pixbuf != null) {
int icon_width = 16, icon_height = 16;
Gtk.icon_size_lookup_for_settings (get_settings (),
Gtk.IconSize.MENU, out icon_width, out icon_height);
pixbuf = pixbuf.scale_simple (icon_width, icon_height, Gdk.InterpType.TILES);
}
renderer.set ("pixbuf", pixbuf);
}
private void selection_changed (Gtk.TreeSelection selection)
{
save_current_note ();
show_note_content (selection);
}
bool button_pressed (Gdk.EventButton event) {
if (event.button == 1) {
if (event.type == Gdk.EventType.2BUTTON_PRESS) {
return show_note_webpage_in_new_tab (event, false);
}
}
if (event.button == 2)
return show_note_webpage_in_new_tab (event, true);
if (event.button == 3)
return show_popup_menu (event);
return false;
}
bool show_note_content (Gtk.TreeSelection selection) {
Gtk.TreeIter iter;
if (selection.get_selected (null, out iter)) {
Note note;
notes_list_store.get (iter, 0, out note);
if (note != current_note) {
note_text_view.buffer.text = note.content;
current_note = note;
}
return true;
} else {
note_text_view.buffer.text = "";
}
return false;
}
bool show_note_webpage_in_new_tab (Gdk.EventButton? event, bool new_tab) {
Gtk.TreeIter iter;
if (notes_tree_view.get_selection ().get_selected (null, out iter)) {
Note note;
notes_list_store.get (iter, 0, out note);
if (note.uri != null) {
var browser = Midori.Browser.get_for_widget (notes_tree_view);
if (new_tab) {
browser.add_uri (note.uri);
} else {
var tab = browser.tab as Midori.View;
tab.set_uri (note.uri);
}
return true;
}
}
return false;
}
bool show_popup_menu (Gdk.EventButton? event) {
return_val_if_fail (event.window == notes_tree_view.get_bin_window(), false);
Gtk.TreePath path = null;
notes_tree_view.get_path_at_pos ((int)event.x, (int)event.y, out path,
null, null, null);
if (path != null) {
Gtk.TreeIter iter;
notes_list_store.get_iter (out iter, path);
Note note;
notes_list_store.get (iter, 0, out note);
var menu = new Gtk.Menu ();
var menuitem = new Gtk.ImageMenuItem.with_label (_("Rename note"));
var image = new Gtk.Image.from_stock (Gtk.STOCK_EDIT, Gtk.IconSize.MENU);
menuitem.always_show_image = true;
menuitem.set_image (image);
menuitem.activate.connect (() => {
notes_tree_view.set_cursor (path,
notes_tree_view.get_column (1), true);
});
menu.append (menuitem);
menuitem = new Gtk.ImageMenuItem.with_label (_("Copy note to clipboard"));
image = new Gtk.Image.from_stock (Gtk.STOCK_COPY, Gtk.IconSize.MENU);
menuitem.always_show_image = true;
menuitem.set_image (image);
menuitem.activate.connect (() => {
get_clipboard (Gdk.SELECTION_CLIPBOARD).set_text (note.content, -1);
});
menu.append (menuitem);
menuitem = new Gtk.ImageMenuItem.with_label (_("Remove note"));
image = new Gtk.Image.from_stock (Gtk.STOCK_DELETE, Gtk.IconSize.MENU);
menuitem.always_show_image = true;
menuitem.set_image (image);
menuitem.activate.connect (() => {
note.remove ();
});
menu.append (menuitem);
menu.show_all ();
Katze.widget_popup (notes_tree_view, menu, null, Katze.MenuPos.CURSOR);
return true;
}
return false;
}
}
private class Manager : Midori.Extension {
internal GLib.List<Gtk.Widget> widgets;
void tab_added (Midori.Browser browser, Midori.Tab tab) {
tab.context_menu.connect (add_menu_items);
}
void add_menu_items (Midori.Tab tab, WebKit.HitTestResult hit_test_result, Midori.ContextAction menu) {
#if !HAVE_WEBKIT2
if ((hit_test_result.context & WebKit.HitTestResultContext.SELECTION) == 0)
return;
#endif
var view = tab as Midori.View;
var action = new Gtk.Action ("Notes", _("Copy selection as note"), null, null);
action.activate.connect ((action)=> {
if (view.has_selection () == true)
{
string selected_text = view.get_selected_text ();
string uri = view.get_display_uri ();
string title = view.get_display_title ();
var note = new Note();
note.add (title, uri, selected_text);
}
});
menu.add (action);
}
void browser_added (Midori.Browser browser) {
var viewable = new Sidebar ();
viewable.show ();
browser.panel.append_page (viewable);
widgets.append (viewable);
foreach (var tab in browser.get_tabs ())
tab_added (browser, tab);
browser.add_tab.connect (tab_added);
}
void activated (Midori.App app) {
string config_path = this.get_config_dir () ?? ":memory:";
string db_path = GLib.Path.build_path (Path.DIR_SEPARATOR_S, config_path, "notes.db");
try {
database = new Midori.Database (db_path);
} catch (Midori.DatabaseError schema_error) {
error (schema_error.message);
}
db = database.db;
widgets = new GLib.List<Gtk.Widget> ();
app.add_browser.connect (browser_added);
foreach (var browser in app.get_browsers ())
browser_added (browser);
}
void deactivated () {
var app = get_app ();
app.add_browser.disconnect (browser_added);
foreach (var widget in widgets)
widget.destroy ();
}
internal Manager () {
GLib.Object (name: _("Notes"),
description: _("Save text clips from websites as notes"),
version: "0.1" + Midori.VERSION_SUFFIX,
authors: "Paweł Forysiuk");
this.activate.connect (activated);
this.deactivate.connect (deactivated);
}
}
}
public Midori.Extension extension_init () {
return new ClipNotes.Manager ();
}

View file

@ -9,7 +9,6 @@
See the file COPYING for the full license text.
*/
#if HAVE_WEBKIT_1_3_8
namespace NSPlugins {
private int active_plugins = 0;
@ -53,10 +52,8 @@ namespace NSPlugins {
}
}
}
#endif
public Katze.Array? extension_init () {
#if HAVE_WEBKIT_1_3_8
if (!Midori.WebSettings.has_plugin_support ())
return null;
@ -70,8 +67,5 @@ public Katze.Array? extension_init () {
extensions.add_item (new NSPlugins.Extension (plugin));
}
return extensions;
#else
return null;
#endif
}

842
extensions/open-with.vala Normal file
View file

@ -0,0 +1,842 @@
/*
Copyright (C) 2014 Christian Dywan <christian@twotoasts.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
#if HAVE_WIN32
namespace Sokoke {
extern static Gdk.Pixbuf get_gdk_pixbuf_from_win32_executable (string path);
}
#endif
namespace ExternalApplications {
/* Spawn the application specified by @app_info on the uri, trying to
remember the association between the content-type and the application
chosen
Returns whether the application was spawned successfully. */
bool open_app_info (AppInfo app_info, string uri, string content_type) {
Midori.URI.recursive_fork_protection (uri, true);
try {
var uris = new List<File> ();
uris.append (File.new_for_uri (uri));
app_info.launch (uris, null);
} catch (Error error) {
warning ("Failed to open \"%s\": %s", uri, error.message);
return false;
}
/* Failing to save the association is a non-fatal error so report success */
try {
new Associations ().remember (content_type, app_info);
} catch (Error error) {
warning ("Failed to save association for \"%s\": %s", uri, error.message);
}
return true;
}
class Associations : Object {
#if HAVE_WIN32
string config_dir;
string filename;
KeyFile keyfile;
public Associations () {
config_dir = Midori.Paths.get_extension_config_dir ("open-with");
filename = Path.build_filename (config_dir, "config");
keyfile = new KeyFile ();
try {
keyfile.load_from_file (filename, KeyFileFlags.NONE);
} catch (FileError.NOENT exist_error) {
/* It's no error if no config file exists */
} catch (Error error) {
warning ("Failed to load associations: %s", error.message);
}
}
/* Determine a handler command-line for @content_type and spawn it on @uri.
Returns whether a handler was found and spawned successfully. */
public bool open (string content_type, string uri) {
Midori.URI.recursive_fork_protection (uri, true);
try {
string commandline = keyfile.get_string ("mimes", content_type);
if ("%u" in commandline)
commandline = commandline.replace ("%u", Shell.quote (uri));
else if ("%F" in commandline)
commandline = commandline.replace ("%F", Shell.quote (Filename.from_uri (uri)));
return Process.spawn_command_line_async (commandline);
} catch (KeyFileError error) {
/* Not remembered before */
return false;
} catch (Error error) {
warning ("Failed to open \"%s\": %s", uri, error.message);
return false;
}
}
/* Save @app_info in the persistent store as the handler for @content_type */
public void remember (string content_type, AppInfo app_info) throws Error {
keyfile.set_string ("mimes", content_type, get_commandline (app_info));
FileUtils.set_contents (filename, keyfile.to_data ());
}
/* Save @commandline in the persistent store as the handler for @content_type */
public void remember_custom_commandline (string content_type, string commandline, string name, string uri) {
keyfile.set_string ("mimes", content_type, commandline);
try {
FileUtils.set_contents (filename, keyfile.to_data ());
} catch (Error error) {
warning ("Failed to remember custom command line for \"%s\": %s", uri, error.message);
}
open (content_type, uri);
}
}
#else
public Associations () {
}
/* Find a handler application for @content_type and spawn it on @uri.
Returns whether a handler was found and spawned successfully. */
public bool open (string content_type, string uri) {
var app_info = AppInfo.get_default_for_type (content_type, false);
if (app_info == null)
return false;
return open_app_info (app_info, uri, content_type);
}
/* Save @app_info as the last-used handler for @content_type */
public void remember (string content_type, AppInfo app_info) throws Error {
app_info.set_as_last_used_for_type (content_type);
app_info.set_as_default_for_type (content_type);
}
/* Save @commandline as a new system MIME handler for @content_type */
public void remember_custom_commandline (string content_type, string commandline, string name, string uri) {
try {
var app_info = AppInfo.create_from_commandline (commandline, name,
"%u" in commandline ? AppInfoCreateFlags.SUPPORTS_URIS : AppInfoCreateFlags.NONE);
open_app_info (app_info, uri, content_type);
} catch (Error error) {
warning ("Failed to remember custom command line for \"%s\": %s", uri, error.message);
}
}
}
#endif
static string get_commandline (AppInfo app_info) {
return app_info.get_commandline () ?? app_info.get_executable ();
}
/* Generate markup of the application's name followed by a description line. */
static string describe_app_info (AppInfo app_info) {
string name = app_info.get_display_name () ?? (Path.get_basename (app_info.get_executable ()));
string desc = app_info.get_description () ?? get_commandline (app_info);
return Markup.printf_escaped ("<b>%s</b>\n%s", name, desc);
}
static Icon? app_info_get_icon (AppInfo app_info) {
#if HAVE_WIN32
return Sokoke.get_gdk_pixbuf_from_win32_executable (app_info.get_executable ());
#else
return app_info.get_icon ();
#endif
}
class CustomizerDialog : Gtk.Dialog {
public Gtk.Entry name_entry;
public Gtk.Entry commandline_entry;
public CustomizerDialog (AppInfo app_info, Gtk.Widget widget) {
var browser = Midori.Browser.get_for_widget (widget);
transient_for = browser;
title = _("Custom…");
#if !HAVE_GTK3
has_separator = false;
#endif
destroy_with_parent = true;
set_icon_name (Gtk.STOCK_OPEN);
resizable = false;
add_buttons (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT);
var vbox = new Gtk.VBox (false, 8);
vbox.border_width = 8;
(get_content_area () as Gtk.Box).pack_start (vbox, true, true, 8);
var sizegroup = new Gtk.SizeGroup (Gtk.SizeGroupMode.HORIZONTAL);
var label = new Gtk.Label (_("Name:"));
sizegroup.add_widget (label);
label.set_alignment (0.0f, 0.5f);
vbox.pack_start (label, false, false, 0);
name_entry = new Gtk.Entry ();
name_entry.activates_default = true;
sizegroup.add_widget (name_entry);
vbox.pack_start (name_entry, true, true, 0);
label = new Gtk.Label (_("Command Line:"));
sizegroup.add_widget (label);
label.set_alignment (0.0f, 0.5f);
vbox.pack_start (label, false, false, 0);
commandline_entry = new Gtk.Entry ();
commandline_entry.activates_default = true;
sizegroup.add_widget (name_entry);
sizegroup.add_widget (commandline_entry);
vbox.pack_start (commandline_entry, true, true, 0);
get_content_area ().show_all ();
set_default_response (Gtk.ResponseType.ACCEPT);
name_entry.text = app_info.get_name ();
commandline_entry.text = get_commandline (app_info);
}
}
private class Chooser : Gtk.VBox {
Gtk.ListStore store = new Gtk.ListStore (1, typeof (AppInfo));
Gtk.TreeView treeview;
List<AppInfo> available;
string content_type;
string uri;
public Chooser (string uri, string content_type) {
this.content_type = content_type;
this.uri = uri;
Gtk.TreeViewColumn column;
treeview = new Gtk.TreeView.with_model (store);
treeview.headers_visible = false;
store.set_sort_column_id (0, Gtk.SortType.ASCENDING);
store.set_sort_func (0, tree_sort_func);
column = new Gtk.TreeViewColumn ();
Gtk.CellRendererPixbuf renderer_icon = new Gtk.CellRendererPixbuf ();
column.pack_start (renderer_icon, false);
column.set_cell_data_func (renderer_icon, on_render_icon);
treeview.append_column (column);
column = new Gtk.TreeViewColumn ();
column.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
Gtk.CellRendererText renderer_text = new Gtk.CellRendererText ();
column.pack_start (renderer_text, true);
column.set_expand (true);
column.set_cell_data_func (renderer_text, on_render_text);
treeview.append_column (column);
treeview.row_activated.connect (row_activated);
treeview.show ();
var scrolled = new Gtk.ScrolledWindow (null, null);
scrolled.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
scrolled.add (treeview);
pack_start (scrolled);
int height;
treeview.create_pango_layout ("a\nb").get_pixel_size (null, out height);
scrolled.set_size_request (-1, height * 5);
treeview.button_release_event.connect (button_released);
treeview.tooltip_text = _("Right-click a suggestion to customize it");
available = new List<AppInfo> ();
foreach (var app_info in AppInfo.get_all_for_type (content_type))
launcher_added (app_info, uri);
if (store.iter_n_children (null) < 1) {
foreach (var app_info in AppInfo.get_all ())
launcher_added (app_info, uri);
}
}
bool button_released (Gdk.EventButton event) {
if (event.button == 3)
return show_popup_menu (event);
return false;
}
bool show_popup_menu (Gdk.EventButton? event) {
Gtk.TreeIter iter;
if (treeview.get_selection ().get_selected (null, out iter)) {
AppInfo app_info;
store.get (iter, 0, out app_info);
var menu = new Gtk.Menu ();
var menuitem = new Gtk.ImageMenuItem.with_mnemonic (_("Custom…"));
menuitem.image = new Gtk.Image.from_stock (Gtk.STOCK_EDIT, Gtk.IconSize.MENU);
menuitem.activate.connect (() => {
customize_app_info (app_info, content_type, uri);
});
menu.append (menuitem);
menu.show_all ();
Katze.widget_popup (treeview, menu, null, Katze.MenuPos.CURSOR);
return true;
}
return false;
}
void customize_app_info (AppInfo app_info, string content_type, string uri) {
var dialog = new CustomizerDialog (app_info, this);
bool accept = dialog.run () == Gtk.ResponseType.ACCEPT;
if (accept) {
string name = dialog.name_entry.text;
string commandline = dialog.commandline_entry.text;
new Associations ().remember_custom_commandline (content_type, commandline, name, uri);
customized (app_info, content_type, uri);
}
dialog.destroy ();
}
public List<AppInfo> get_available () {
return available.copy ();
}
public AppInfo get_app_info () {
Gtk.TreeIter iter;
if (treeview.get_selection ().get_selected (null, out iter)) {
AppInfo app_info;
store.get (iter, 0, out app_info);
return app_info;
}
assert_not_reached ();
}
void on_render_icon (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
AppInfo app_info;
model.get (iter, 0, out app_info);
renderer.set ("gicon", app_info_get_icon (app_info),
"stock-size", Gtk.IconSize.DIALOG,
"xpad", 4);
}
void on_render_text (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
AppInfo app_info;
model.get (iter, 0, out app_info);
renderer.set ("markup", describe_app_info (app_info),
"ellipsize", Pango.EllipsizeMode.END);
}
void launcher_added (AppInfo app_info, string uri) {
if (!app_info.should_show ())
return;
Gtk.TreeIter iter;
store.append (out iter);
store.set (iter, 0, app_info);
available.append (app_info);
}
int tree_sort_func (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b) {
AppInfo app_info1, app_info2;
model.get (a, 0, out app_info1);
model.get (b, 0, out app_info2);
return strcmp (app_info1.get_display_name (), app_info2.get_display_name ());
}
void row_activated (Gtk.TreePath path, Gtk.TreeViewColumn column) {
Gtk.TreeIter iter;
if (store.get_iter (out iter, path)) {
AppInfo app_info;
store.get (iter, 0, out app_info);
selected (app_info);
}
}
public signal void selected (AppInfo app_info);
public signal void customized (AppInfo app_info, string content_type, string uri);
}
class ChooserDialog : Gtk.Dialog {
public Chooser chooser { get; private set; }
public ChooserDialog (string uri, string content_type, Gtk.Widget widget) {
string filename;
if (uri.has_prefix ("file://"))
filename = Midori.Download.get_basename_for_display (uri);
else
filename = uri;
var browser = Midori.Browser.get_for_widget (widget);
transient_for = browser;
title = _("Choose application");
#if !HAVE_GTK3
has_separator = false;
#endif
destroy_with_parent = true;
set_icon_name (Gtk.STOCK_OPEN);
resizable = true;
add_buttons (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT);
var vbox = new Gtk.VBox (false, 8);
vbox.border_width = 8;
(get_content_area () as Gtk.Box).pack_start (vbox, true, true, 8);
var label = new Gtk.Label (_("Select an application to open \"%s\"".printf (filename)));
label.ellipsize = Pango.EllipsizeMode.MIDDLE;
vbox.pack_start (label, false, false, 0);
if (uri == "")
label.no_show_all = true;
chooser = new Chooser (uri, content_type);
vbox.pack_start (chooser, true, true, 0);
get_content_area ().show_all ();
Gtk.Requisition req;
#if HAVE_GTK3
get_content_area ().get_preferred_size (null, out req);
#else
get_content_area ().size_request (out req);
#endif
(this as Gtk.Window).set_default_size (req.width*2, req.height*3/2);
set_default_response (Gtk.ResponseType.ACCEPT);
chooser.selected.connect ((app_info) => {
response (Gtk.ResponseType.ACCEPT);
});
chooser.customized.connect ((app_info, content_type, uri) => {
response (Gtk.ResponseType.CANCEL);
});
}
public AppInfo? open_with () {
show ();
bool accept = run () == Gtk.ResponseType.ACCEPT;
hide ();
if (!accept)
return null;
return chooser.get_app_info ();
}
}
class ChooserButton : Gtk.Button {
public AppInfo? app_info { get; set; }
public string? commandline { get; set; }
ChooserDialog dialog;
Gtk.Label app_name;
Gtk.Image icon;
public ChooserButton (string mime_type, string? commandline) {
string content_type = ContentType.from_mime_type (mime_type);
dialog = new ChooserDialog ("", content_type, this);
app_info = null;
foreach (var candidate in dialog.chooser.get_available ()) {
if (get_commandline (candidate) == commandline)
app_info = candidate;
}
var hbox = new Gtk.HBox (false, 4);
icon = new Gtk.Image ();
hbox.pack_start (icon, false, false, 0);
app_name = new Gtk.Label (null);
app_name.use_markup = true;
app_name.ellipsize = Pango.EllipsizeMode.END;
hbox.pack_start (app_name, true, true, 0);
add (hbox);
show_all ();
update_label ();
clicked.connect (() => {
dialog.transient_for = this.get_toplevel () as Gtk.Window;
app_info = dialog.open_with ();
string new_commandline = app_info != null ? get_commandline (app_info) : null;
commandline = new_commandline;
selected (new_commandline);
update_label ();
});
}
void update_label () {
app_name.label = app_info != null ? describe_app_info (app_info).replace ("\n", " ") : _("None");
icon.set_from_gicon (app_info != null ? app_info_get_icon (app_info) : null, Gtk.IconSize.BUTTON);
}
public signal void selected (string? commandline);
}
class Types : Gtk.VBox {
public Gtk.ListStore store = new Gtk.ListStore (2, typeof (string), typeof (AppInfo));
Gtk.TreeView treeview;
public Types () {
Gtk.TreeViewColumn column;
treeview = new Gtk.TreeView.with_model (store);
treeview.headers_visible = false;
store.set_sort_column_id (0, Gtk.SortType.ASCENDING);
store.set_sort_func (0, tree_sort_func);
column = new Gtk.TreeViewColumn ();
column.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
Gtk.CellRendererPixbuf renderer_type_icon = new Gtk.CellRendererPixbuf ();
column.pack_start (renderer_type_icon, false);
column.set_cell_data_func (renderer_type_icon, on_render_type_icon);
treeview.append_column (column);
column = new Gtk.TreeViewColumn ();
column.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
Gtk.CellRendererText renderer_type_text = new Gtk.CellRendererText ();
column.pack_start (renderer_type_text, true);
column.set_cell_data_func (renderer_type_text, on_render_type_text);
treeview.append_column (column);
column = new Gtk.TreeViewColumn ();
column.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
Gtk.CellRendererPixbuf renderer_icon = new Gtk.CellRendererPixbuf ();
column.pack_start (renderer_icon, false);
column.set_cell_data_func (renderer_icon, on_render_icon);
treeview.append_column (column);
column = new Gtk.TreeViewColumn ();
column.set_sizing (Gtk.TreeViewColumnSizing.AUTOSIZE);
Gtk.CellRendererText renderer_text = new Gtk.CellRendererText ();
column.pack_start (renderer_text, true);
column.set_expand (true);
column.set_cell_data_func (renderer_text, on_render_text);
treeview.append_column (column);
treeview.row_activated.connect (row_activated);
treeview.show ();
var scrolled = new Gtk.ScrolledWindow (null, null);
scrolled.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
scrolled.add (treeview);
pack_start (scrolled);
int height;
treeview.create_pango_layout ("a\nb").get_pixel_size (null, out height);
scrolled.set_size_request (-1, height * 5);
foreach (unowned string content_type in ContentType.list_registered ())
launcher_added (content_type);
foreach (unowned string scheme in Vfs.get_default ().get_supported_uri_schemes ())
launcher_added ("x-scheme-handler/" + scheme);
treeview.size_allocate.connect_after ((allocation) => {
treeview.columns_autosize ();
});
}
void on_render_type_icon (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
string content_type;
store.get (iter, 0, out content_type);
renderer.set ("gicon", ContentType.get_icon (content_type),
"stock-size", Gtk.IconSize.BUTTON,
"xpad", 4);
}
void on_render_type_text (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
string content_type;
AppInfo app_info;
store.get (iter, 0, out content_type, 1, out app_info);
string desc, mime_type;
if (content_type.has_prefix ("x-scheme-handler/")) {
desc = content_type.split ("/")[1] + "://";
mime_type = "";
} else {
desc = ContentType.get_description (content_type);
mime_type = ContentType.get_mime_type (content_type);
}
renderer.set ("markup",
Markup.printf_escaped ("<b>%s</b>\n%s",
desc, mime_type),
#if HAVE_GTK3
"max-width-chars", 30,
#else
"width-chars", 30,
#endif
"ellipsize", Pango.EllipsizeMode.END);
}
void on_render_icon (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
AppInfo app_info;
model.get (iter, 1, out app_info);
renderer.set ("gicon", app_info_get_icon (app_info),
"stock-size", Gtk.IconSize.MENU,
"xpad", 4);
}
void on_render_text (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
AppInfo app_info;
model.get (iter, 1, out app_info);
renderer.set ("markup", describe_app_info (app_info),
"ellipsize", Pango.EllipsizeMode.END);
}
void launcher_added (string content_type) {
var app_info = AppInfo.get_default_for_type (content_type, false);
if (app_info == null)
return;
Gtk.TreeIter iter;
store.append (out iter);
store.set (iter, 0, content_type, 1, app_info);
}
int tree_sort_func (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b) {
string content_type1, content_type2;
model.get (a, 0, out content_type1);
model.get (b, 0, out content_type2);
return strcmp (content_type1, content_type2);
}
void row_activated (Gtk.TreePath path, Gtk.TreeViewColumn column) {
Gtk.TreeIter iter;
if (store.get_iter (out iter, path)) {
string content_type;
store.get (iter, 0, out content_type);
selected (content_type, iter);
}
}
public signal void selected (string content_type, Gtk.TreeIter iter);
}
private class Manager : Midori.Extension {
enum NextStep {
TRY_OPEN,
OPEN_WITH
}
bool open_uri (Midori.Tab tab, string uri) {
string content_type = get_content_type (uri, null);
return open_with_type (uri, content_type, tab, NextStep.TRY_OPEN);
}
bool navigation_requested (Midori.Tab tab, string uri) {
/* FIXME: Make this a list of known/internal uris to pass-thru */
if (uri.has_prefix ("file://") || Midori.URI.is_http (uri) || Midori.URI.is_blank (uri))
return false;
/* Don't find app for abp links, we should handle them internally */
if (uri.has_prefix("abp:"))
return true;
string content_type = get_content_type (uri, null);
open_with_type (uri, content_type, tab, NextStep.TRY_OPEN);
return true;
}
string get_content_type (string uri, string? mime_type) {
if (!uri.has_prefix ("file://") && !Midori.URI.is_http (uri)) {
string protocol = uri.split(":", 2)[0];
return "x-scheme-handler/" + protocol;
} else if (mime_type == null) {
string filename;
bool uncertain;
try {
filename = Filename.from_uri (uri);
} catch (Error error) {
filename = uri;
}
return ContentType.guess (filename, null, out uncertain);
}
return ContentType.from_mime_type (mime_type);
}
/* Returns %TRUE if the attempt to download and open failed immediately, %FALSE otherwise */
bool open_with_type (string uri, string content_type, Gtk.Widget widget, NextStep next_step) {
#if HAVE_WEBKIT2
return open_now (uri, content_type, widget, next_step);
#else
if (!Midori.URI.is_http (uri))
return open_now (uri, content_type, widget, next_step);
/* Don't download websites */
if (content_type == "application/octet-stream")
return open_now (uri, content_type, widget, next_step);
var download = new WebKit.Download (new WebKit.NetworkRequest (uri));
download.destination_uri = Midori.Download.prepare_destination_uri (download, null);
if (!Midori.Download.has_enough_space (download, download.destination_uri))
return false;
download.notify["status"].connect ((pspec) => {
if (download.status == WebKit.DownloadStatus.FINISHED) {
open_now (download.destination_uri, content_type, widget, next_step);
}
else if (download.status == WebKit.DownloadStatus.ERROR)
Midori.show_message_dialog (Gtk.MessageType.ERROR,
_("Download error"),
_("Cannot open '%s' because the download failed."
).printf (download.destination_uri), false);
});
download.start ();
return true;
#endif
}
/* If @next_step is %NextStep.TRY_OPEN, tries to pick a handler automatically.
If the automatic handler did not exist or could not run, asks for an application.
Returns whether an application was found and launched successfully. */
bool open_now (string uri, string content_type, Gtk.Widget widget, NextStep next_step) {
if (next_step == NextStep.TRY_OPEN && (new Associations ()).open (content_type, uri))
return true;
/* if opening directly failed or wasn't tried, ask for an association */
if (open_with (uri, content_type, widget) != null)
return true;
return false;
}
/* Returns the application chosen to open the uri+content_type if the application
was spawned successfully, %NULL if none was chosen or running was unsuccessful. */
AppInfo? open_with (string uri, string content_type, Gtk.Widget widget) {
var dialog = new ChooserDialog (uri, content_type, widget);
var app_info = dialog.open_with ();
dialog.destroy ();
if (uri == "")
return app_info;
if (app_info == null)
return app_info;
return open_app_info (app_info, uri, content_type) ? app_info : null;
}
void context_menu (Midori.Tab tab, WebKit.HitTestResult hit_test_result, Midori.ContextAction menu) {
if ((hit_test_result.context & WebKit.HitTestResultContext.LINK) != 0) {
string uri = hit_test_result.link_uri;
var action = new Gtk.Action ("OpenWith", _("Open _with…"), null, null);
action.activate.connect ((action) => {
open_with_type (uri, get_content_type (uri, null), tab, NextStep.OPEN_WITH);
});
menu.add (action);
}
#if !HAVE_WEBKIT2
if ((hit_test_result.context & WebKit.HitTestResultContext.IMAGE) != 0) {
string uri = hit_test_result.image_uri;
var action = new Gtk.Action ("OpenImageInViewer", _("Open in Image _Viewer"), null, null);
action.activate.connect ((action) => {
open_with_type (uri, get_content_type (uri, null), tab, NextStep.TRY_OPEN);
});
menu.add (action);
}
#endif
}
void show_preferences (Katze.Preferences preferences) {
var settings = get_app ().settings;
var category = preferences.add_category (_("File Types"), Gtk.STOCK_FILE);
preferences.add_group (null);
var sizegroup = new Gtk.SizeGroup (Gtk.SizeGroupMode.HORIZONTAL);
var label = new Gtk.Label (_("Text Editor"));
sizegroup.add_widget (label);
label.set_alignment (0.0f, 0.5f);
preferences.add_widget (label, "indented");
var entry = new ChooserButton ("text/plain", settings.text_editor);
sizegroup.add_widget (entry);
entry.selected.connect ((commandline) => {
settings.text_editor = commandline;
});
preferences.add_widget (entry, "spanned");
label = new Gtk.Label (_("News Aggregator"));
sizegroup.add_widget (label);
label.set_alignment (0.0f, 0.5f);
preferences.add_widget (label, "indented");
entry = new ChooserButton ("application/rss+xml", settings.news_aggregator);
sizegroup.add_widget (entry);
entry.selected.connect ((commandline) => {
settings.news_aggregator = commandline;
});
preferences.add_widget (entry, "spanned");
var types = new Types ();
types.selected.connect ((content_type, iter) => {
var app_info = open_with ("", content_type, preferences);
if (app_info == null)
return;
try {
app_info.set_as_default_for_type (content_type);
types.store.set (iter, 1, app_info);
} catch (Error error) {
warning ("Failed to select default for \"%s\": %s", content_type, error.message);
}
});
category.pack_start (types, true, true, 0);
types.show_all ();
}
public void tab_added (Midori.Browser browser, Midori.View view) {
view.navigation_requested.connect_after (navigation_requested);
view.open_uri.connect (open_uri);
view.context_menu.connect (context_menu);
}
public void tab_removed (Midori.Browser browser, Midori.View view) {
view.navigation_requested.disconnect (navigation_requested);
view.open_uri.disconnect (open_uri);
view.context_menu.disconnect (context_menu);
}
void browser_added (Midori.Browser browser) {
foreach (var tab in browser.get_tabs ())
tab_added (browser, tab);
browser.add_tab.connect (tab_added);
browser.remove_tab.connect (tab_removed);
browser.show_preferences.connect (show_preferences);
}
void activated (Midori.App app) {
foreach (var browser in app.get_browsers ())
browser_added (browser);
app.add_browser.connect (browser_added);
}
void browser_removed (Midori.Browser browser) {
foreach (var tab in browser.get_tabs ())
tab_removed (browser, tab);
browser.add_tab.disconnect (tab_added);
browser.remove_tab.disconnect (tab_removed);
browser.show_preferences.disconnect (show_preferences);
}
void deactivated () {
var app = get_app ();
foreach (var browser in app.get_browsers ())
browser_removed (browser);
app.add_browser.disconnect (browser_added);
}
internal Manager () {
GLib.Object (name: "External Applications",
description: "Choose what to open unknown file types with",
version: "0.1" + Midori.VERSION_SUFFIX,
authors: "Christian Dywan <christian@twotoasts.de>");
this.activate.connect (activated);
this.deactivate.connect (deactivated);
}
}
}
public Midori.Extension extension_init () {
return new ExternalApplications.Manager ();
}

View file

@ -48,21 +48,12 @@ clock_set_current_time (MidoriBrowser* browser)
GtkWidget* label = g_object_get_data (G_OBJECT (browser), "clock-label");
const gchar* format = midori_extension_get_string (extension, "format");
#if GLIB_CHECK_VERSION (2, 26, 0)
GDateTime* date = g_date_time_new_now_local ();
gint seconds = g_date_time_get_seconds (date);
gchar* pretty = g_date_time_format (date, format);
gtk_label_set_label (GTK_LABEL (label), pretty);
g_free (pretty);
g_date_time_unref (date);
#else
time_t rawtime = time (NULL);
struct tm *tm = localtime (&rawtime);
gint seconds = tm->tm_sec;
char date_fmt[512];
strftime (date_fmt, sizeof (date_fmt), format, tm);
gtk_label_set_label (GTK_LABEL (label), date_fmt);
#endif
if (g_strstr_len (format, -1, "%c")
|| g_strstr_len (format, -1, "%N")

View file

@ -126,7 +126,7 @@ statusbar_features_property_proxy (MidoriWebSettings* settings,
else if (!strcmp (property, "zoom-level"))
{
MidoriBrowser* browser = midori_browser_get_for_widget (toolbar);
gint i;
guint i;
button = gtk_combo_box_text_new_with_entry ();
gtk_entry_set_width_chars (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (button))), 4);
for (i = 0; i < G_N_ELEMENTS (zoom_levels); i++)
@ -153,19 +153,13 @@ statusbar_features_property_proxy (MidoriWebSettings* settings,
image = gtk_image_new_from_stock (STOCK_IMAGE, GTK_ICON_SIZE_MENU);
gtk_button_set_image (GTK_BUTTON (button), image);
gtk_widget_set_tooltip_text (button, _("Load images automatically"));
statusbar_features_toolbar_notify_toolbar_style_cb (toolbar, NULL, button);
g_signal_connect (toolbar, "notify::toolbar-style",
G_CALLBACK (statusbar_features_toolbar_notify_toolbar_style_cb), button);
}
if (!strcmp (property, "enable-javascript"))
else if (!strcmp (property, "enable-javascript"))
{
g_object_set_data (G_OBJECT (button), "feature-label", _("Scripts"));
image = gtk_image_new_from_stock (STOCK_SCRIPT, GTK_ICON_SIZE_MENU);
gtk_button_set_image (GTK_BUTTON (button), image);
gtk_widget_set_tooltip_text (button, _("Enable scripts"));
statusbar_features_toolbar_notify_toolbar_style_cb (toolbar, NULL, button);
g_signal_connect (toolbar, "notify::toolbar-style",
G_CALLBACK (statusbar_features_toolbar_notify_toolbar_style_cb), button);
}
else if (!strcmp (property, "enable-plugins"))
{
@ -175,6 +169,9 @@ statusbar_features_property_proxy (MidoriWebSettings* settings,
image = gtk_image_new_from_stock (MIDORI_STOCK_PLUGINS, GTK_ICON_SIZE_MENU);
gtk_button_set_image (GTK_BUTTON (button), image);
gtk_widget_set_tooltip_text (button, _("Enable Netscape plugins"));
}
if (GTK_IS_TOOLBAR (toolbar) && GTK_IS_BUTTON (button))
{
statusbar_features_toolbar_notify_toolbar_style_cb (toolbar, NULL, button);
g_signal_connect (toolbar, "notify::toolbar-style",
G_CALLBACK (statusbar_features_toolbar_notify_toolbar_style_cb), button);

View file

@ -288,20 +288,6 @@ tab_panel_settings_notify_cb (MidoriWebSettings* settings,
gtk_tree_store_set (GTK_TREE_STORE (model), &iter, 2, buttons, -1);
}
static void
tab_panel_toggle_toolbook (GtkWidget* toolbar)
{
/* Hack to ensure correct toolbar visibility */
GtkWidget* toolbook = gtk_widget_get_parent (toolbar);
if (gtk_notebook_get_current_page (GTK_NOTEBOOK (toolbook))
== gtk_notebook_page_num (GTK_NOTEBOOK (toolbook), toolbar))
{
GList* items = gtk_container_get_children (GTK_CONTAINER (toolbar));
sokoke_widget_set_visible (toolbook, items != NULL);
g_list_free (items);
}
}
static void
tab_panel_remove_view (MidoriBrowser* browser,
GtkWidget* view,
@ -310,9 +296,7 @@ tab_panel_remove_view (MidoriBrowser* browser,
if (minimized)
{
GtkToolItem* toolitem = tab_panel_get_toolitem_for_view (view);
GtkWidget* toolbar = tab_panel_get_toolbar_for_browser (browser);
gtk_widget_destroy (GTK_WIDGET (toolitem));
tab_panel_toggle_toolbook (toolbar);
}
else
{
@ -443,7 +427,6 @@ tab_panel_browser_add_tab_cb (MidoriBrowser* browser,
g_object_set_data (G_OBJECT (view), "tab-panel-ext-toolitem", toolitem);
gtk_widget_show (GTK_WIDGET (toolitem));
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), toolitem, -1);
tab_panel_toggle_toolbook (toolbar);
g_signal_connect (toolitem, "clicked",
G_CALLBACK (tab_panel_toolitem_clicked_cb), view);
g_signal_connect (gtk_bin_get_child (GTK_BIN (toolitem)), "button-press-event",
@ -574,10 +557,17 @@ tab_panel_app_add_browser_cb (MidoriApp* app,
toolbar = gtk_toolbar_new ();
g_object_set_data (G_OBJECT (browser), "tab-panel-ext-toolbar", toolbar);
gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_BOTH_HORIZ);
gtk_toolbar_set_icon_size (GTK_TOOLBAR (toolbar), GTK_ICON_SIZE_BUTTON);
gtk_widget_show (toolbar);
GtkActionGroup* actions = midori_browser_get_action_group (browser);
GtkAction* action = gtk_action_group_get_action (actions, "TabNew");
GtkWidget* toolitem = gtk_action_create_tool_item (action);
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (toolitem), -1);
action = gtk_action_group_get_action (actions, "Separator");
toolitem = gtk_action_create_tool_item (action);
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (toolitem), -1);
/*
TODO: Implement optional thumbnail images
toolitem = gtk_toggle_tool_button_new_from_stock (STOCK_IMAGE);

Some files were not shown because too many files have changed in this diff Show more