├── debian ├── compat ├── source │ └── format ├── rules ├── README ├── README.Debian ├── README.source ├── changelog ├── control └── copyright ├── .gitignore ├── lib ├── meson.build ├── synapse-core │ ├── config.h.in │ ├── meson.build │ ├── plugin.vala │ ├── relevancy-service.vala │ ├── result-set.vala │ ├── match.vala │ ├── dbus-service.vala │ ├── volume-service.vala │ ├── config-service.vala │ ├── query.vala │ ├── relevancy-backend-zg.vala │ ├── utils.vala │ └── common-actions.vala └── synapse-plugins │ ├── meson.build │ ├── web-search.vala │ ├── link-plugin.vala │ ├── command-plugin.vala │ └── calculator-plugin.vala ├── data ├── screenshot.png ├── launchy.desktop ├── applications-menu.css ├── meson.build ├── icons.gresource.xml ├── icons │ ├── 32 │ │ └── launchy.svg │ ├── 48 │ │ └── launchy.svg │ ├── 64 │ │ └── launchy.svg │ ├── panther-view-list.svg │ ├── panther-icon-index.svg │ └── panther-bookmarks.svg ├── org.enso.launchy.gschema.xml └── panther-applications.menu ├── vapi ├── CMakeLists.txt ├── config.vapi ├── gl.vapi └── cogl-fixes.vapi ├── .vscode ├── settings.json └── launch.json ├── deps.sh ├── src ├── Pixels.vala ├── Utils.vala ├── meson.build ├── Backend │ ├── Plank.vala │ ├── DBusService.vala │ ├── AppSystem.vala │ └── SynapseSearch.vala ├── Settings.vala ├── Widgets │ ├── Sidebar.vala │ ├── SearchItem.vala │ ├── CategoryView.vala │ ├── Switcher.vala │ ├── StaredView.vala │ ├── Grid.vala │ └── AppEntry.vala └── Launchy.vala ├── README.md ├── meson └── post_install.py └── meson.build /debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | build/ 3 | builddir/ 4 | .vscode/ 5 | -------------------------------------------------------------------------------- /lib/meson.build: -------------------------------------------------------------------------------- 1 | subdir('synapse-core') 2 | subdir('synapse-plugins') -------------------------------------------------------------------------------- /data/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nick92/launchy/HEAD/data/screenshot.png -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | BUILDDIR = build_dir 4 | 5 | %: 6 | dh $@ 7 | 8 | -------------------------------------------------------------------------------- /vapi/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ### CMakeLists automatically created with AutoVala 2 | ### Do not edit 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "C_Cpp.default.configurationProvider": "vector-of-bool.cmake-tools" 3 | } -------------------------------------------------------------------------------- /deps.sh: -------------------------------------------------------------------------------- 1 | sudo apt install libgee-0.8-dev libgnome-menu-3-dev libgtk-3-dev libjson-glib-dev libplank-dev libsoup2.4-dev libunity-dev libwnck-3-dev valac -------------------------------------------------------------------------------- /debian/README: -------------------------------------------------------------------------------- 1 | The Debian Package panther-launcher 2 | ---------------------------- 3 | 4 | Comments regarding the Package 5 | 6 | -- Nick Wilkins Tue, 01 Aug 2017 20:53:44 +0100 7 | -------------------------------------------------------------------------------- /debian/README.Debian: -------------------------------------------------------------------------------- 1 | panther-launcher for Debian 2 | -------------------------- 3 | 4 | 5 | 6 | -- Nick Wilkins Tue, 01 Aug 2017 20:53:44 +0100 7 | -------------------------------------------------------------------------------- /src/Pixels.vala: -------------------------------------------------------------------------------- 1 | namespace Launcher.Pixels { 2 | const int PADDING = 25; 3 | const int ROW_SPACING = 10; 4 | const int BOTTOM_SPACE = 128; 5 | const int SIDEBAR_GRID_PADDING = 15; 6 | int ITEM_SIZE = 160; 7 | int SIDEBAR_WIDTH = 0; 8 | } 9 | -------------------------------------------------------------------------------- /data/launchy.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Launchy 3 | Comment=Appliation launcher 4 | Exec=launchy 5 | Icon=preferences-system-login 6 | Terminal=false 7 | Type=Application 8 | NoDisplay=true 9 | X-GNOME-AutoRestart=true 10 | X-GNOME-Autostart-Phase=Applications -------------------------------------------------------------------------------- /data/applications-menu.css: -------------------------------------------------------------------------------- 1 | .searchbox { 2 | border-radius: 25px; 3 | min-height: 30px; 4 | min-width: 30px; 5 | margin: 5px; 6 | padding: 5px; 7 | } 8 | 9 | .view_button { 10 | background: none; 11 | border: none; 12 | } 13 | 14 | /* .sidebar { 15 | background-color: rgba(246, 248, 250, 0.95); 16 | } */ -------------------------------------------------------------------------------- /lib/synapse-core/config.h.in: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | #define DATADIR "@DATADIR@" 5 | #define PKGDATADIR "@PKGDATADIR@" 6 | #define GETTEXT_PACKAGE "@GETTEXT_PACKAGE@" 7 | #define RELEASE_NAME "@RELEASE_NAME@" 8 | #define VERSION "@VERSION@" 9 | #define VERSION_INFO "@VERSION_INFO@" 10 | 11 | #endif // CONFIG_H 12 | -------------------------------------------------------------------------------- /debian/README.source: -------------------------------------------------------------------------------- 1 | panther-launcher for Debian 2 | -------------------------- 3 | 4 | 6 | 7 | 8 | 9 | -- Nick Wilkins Tue, 01 Aug 2017 20:53:44 +0100 10 | 11 | -------------------------------------------------------------------------------- /vapi/config.vapi: -------------------------------------------------------------------------------- 1 | [CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "config.h")] 2 | namespace Build { 3 | public const string DATADIR; 4 | public const string PKGDATADIR; 5 | public const string GETTEXT_PACKAGE; 6 | public const string RELEASE_NAME; 7 | public const string VERSION; 8 | public const string VERSION_INFO; 9 | } 10 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | enso-launchy (1.0.20) bionic; urgency=medium 2 | 3 | * kill launchy on screen rezise 4 | 5 | -- Nick Wilkins Fri, 27 Apr 2018 20:53:44 +0100 6 | 7 | 8 | enso-launchy (1.0.19) bionic; urgency=medium 9 | 10 | * First commit of launchy 11 | 12 | -- Nick Wilkins Mon, 19 Mar 2018 20:53:44 +0100 13 | -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | 2 | install_data( 3 | 'icons.gresource.xml', 4 | install_dir: join_paths('share', 'glib-2.0', 'schemas')) 5 | 6 | install_data( 7 | 'org.enso.launchy.gschema.xml', 8 | install_dir: join_paths('share', 'glib-2.0', 'schemas')) 9 | 10 | install_data('launchy.desktop', 11 | install_dir : join_paths('share', 'applications')) 12 | 13 | install_data( 14 | 'panther-applications.menu', 15 | install_dir: join_paths(get_option('sysconfdir'), 'xdg', 'menus') 16 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # launchy 2 | 3 | ![launchy](data/screenshot.png) 4 | 5 | ## Installing and Running 6 | 7 | ### Dependencies 8 | 9 | libgee-0.8-dev 10 | libgnome-menu-3-dev 11 | libgtk-3-dev 12 | libjson-glib-dev 13 | libplank-dev 14 | libsoup2.4-dev 15 | libunity-dev 16 | libwnck-3-dev 17 | libzeitgeist-2.0-dev (optional) 18 | pkg-config 19 | valac 20 | 21 | ### Build and install 22 | 23 | Just type from a command line: 24 | 25 | meson build --prefix=/usr 26 | cd build 27 | ninja 28 | sudo make install 29 | ./src/launchy 30 | -------------------------------------------------------------------------------- /data/icons.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icons/panther-icon-index.svg 5 | icons/panther-view-list.svg 6 | icons/panther-bookmarks.svg 7 | 8 | 9 | applications-menu.css 10 | 11 | 12 | -------------------------------------------------------------------------------- /vapi/gl.vapi: -------------------------------------------------------------------------------- 1 | /* gl.vapi generated by vapigen, do not modify. */ 2 | 3 | [CCode (cprefix = "GL", gir_namespace = "GL", gir_version = "1.0", lower_case_cprefix = "gl_")] 4 | namespace GL { 5 | [CCode (cheader_filename = "GL/gl.h", cname = "GLint")] 6 | [SimpleType] 7 | public struct GLint : int { 8 | } 9 | [CCode (cheader_filename = "GL/gl.h", cname = "GLenum")] 10 | [SimpleType] 11 | public struct GLenum : uint { 12 | } 13 | [CCode (cheader_filename = "GL/gl.h", cname = "GL_MAX_TEXTURE_SIZE")] 14 | public const int GL_MAX_TEXTURE_SIZE; 15 | [CCode (cheader_filename = "GL/gl.h", cname = "glGetIntegerv")] 16 | public static void glGetIntegerv (GL.GLenum pname, out unowned GL.GLint @params); 17 | } 18 | -------------------------------------------------------------------------------- /meson/post_install.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import subprocess 5 | 6 | prefix = os.environ.get('MESON_INSTALL_PREFIX', '/usr') 7 | datadir = os.path.join(prefix, 'share') 8 | 9 | # Packaging tools define DESTDIR and this isn't needed for them 10 | if 'DESTDIR' not in os.environ: 11 | print('Compiling gsettings schemas...') 12 | subprocess.call(['glib-compile-schemas', os.path.join(datadir, 'glib-2.0', 'schemas')]) 13 | 14 | print('Updating icon cache...') 15 | subprocess.call(['gtk-update-icon-cache', '-qtf', os.path.join(datadir, 'icons', 'hicolor')]) 16 | 17 | print('Updating desktop database...') 18 | subprocess.call(['update-desktop-database', '-q', os.path.join(datadir, 'applications')]) 19 | 20 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: enso-launchy 2 | Section: x11 3 | Priority: optional 4 | Maintainer: Nick Wilkins 5 | Build-Depends: cmake (>= 2.8), 6 | debhelper (>= 9), 7 | valac (>= 0.26), 8 | libatk1.0-dev, 9 | libplank-dev (>= 0.11.0), 10 | libcairo2-dev, 11 | libgtk-3-dev, 12 | libgdk-pixbuf2.0-dev, 13 | libgee-0.8-dev, 14 | libglib2.0-dev, 15 | libjson-glib-dev, 16 | libgnome-menu-3-dev, 17 | libsoup2.4-dev, 18 | libpango1.0-dev, 19 | libx11-dev, 20 | gettext, 21 | intltool 22 | Standards-Version: 3.9.6 23 | 24 | Package: enso-launchy 25 | Architecture: any 26 | Depends: ${misc:Depends}, ${shlibs:Depends} 27 | Description: Search and launch applications with ease with Lauchy 28 | -------------------------------------------------------------------------------- /lib/synapse-plugins/meson.build: -------------------------------------------------------------------------------- 1 | synapse_plugins_sources = [ 2 | 'calculator-plugin.vala', 3 | 'command-plugin.vala', 4 | 'desktop-file-plugin.vala', 5 | 'system-managment.vala', 6 | 'link-plugin.vala', 7 | 'web-search.vala' 8 | ] 9 | 10 | synapse_plugins_deps = [ 11 | glib_dep, 12 | gio_unix_dep, 13 | json_glib_dep, 14 | gee_dep, 15 | gtk_dep, 16 | synapse_core_dep 17 | ] 18 | 19 | synapse_plugins = static_library('synapse-plugins', 20 | synapse_plugins_sources, 21 | link_with: synapse_core, 22 | dependencies: synapse_plugins_deps, 23 | c_args : '-DGMENU_I_KNOW_THIS_IS_UNSTABLE', 24 | install : true) 25 | 26 | synapse_plugins_dep = declare_dependency( 27 | link_with: synapse_plugins, 28 | include_directories: include_directories('.') 29 | ) -------------------------------------------------------------------------------- /vapi/cogl-fixes.vapi: -------------------------------------------------------------------------------- 1 | namespace CoglFixes 2 | { 3 | [CCode (cname = "cogl_texture_get_data")] 4 | public int texture_get_data (Cogl.Texture texture, Cogl.PixelFormat format, uint rowstride, [CCode (array_length = false)] uint8[] pixels); 5 | 6 | [CCode (cname = "cogl_material_set_user_program")] 7 | public void set_user_program (Cogl.Material material, Cogl.Handle program); 8 | 9 | [CCode (cname = "cogl_program_set_uniform_1f")] 10 | public void set_uniform_1f (Cogl.Program program, int uniform_no, float value); 11 | [CCode (cname = "cogl_program_set_uniform_1i")] 12 | public void set_uniform_1i (Cogl.Program program, int uniform_no, int value); 13 | 14 | [CCode (cname = "cogl_get_source")] 15 | public void *get_source (); 16 | 17 | [CCode (cname = "cogl_pop_source")] 18 | public void pop_source (); 19 | [CCode (cname = "cogl_push_source")] 20 | public void push_source (void *material_or_pipeline); 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "(gdb) Launch", 9 | "type": "cppdbg", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/build/src/launchy", 12 | "args": [], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceFolder}", 15 | "environment": [], 16 | "externalConsole": true, 17 | "MIMode": "gdb", 18 | "setupCommands": [ 19 | { 20 | "description": "Enable pretty-printing for gdb", 21 | "text": "-enable-pretty-printing", 22 | "ignoreFailures": true 23 | } 24 | ] 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /src/Utils.vala: -------------------------------------------------------------------------------- 1 | // -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- 2 | // 3 | // Copyright (C) 2011-2012 Panther Developers 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | 19 | namespace Launcher { 20 | 21 | class Utils : GLib.Object { 22 | 23 | public static int sort_apps_by_name (Backend.App a, Backend.App b) { 24 | return a.name.collate (b.name); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | sources = [ 2 | 'Launchy.vala', 3 | 'LaunchyView.vala', 4 | 'Settings.vala', 5 | 'Utils.vala', 6 | 'Pixels.vala', 7 | 8 | 'Backend/App.vala', 9 | 'Backend/AppSystem.vala', 10 | 'Backend/DBusService.vala', 11 | 'Backend/Plank.vala', 12 | #'Backend/RelevancyService.vala', 13 | 'Backend/SynapseSearch.vala', 14 | 15 | 'Widgets/AppEntry.vala', 16 | 'Widgets/Grid.vala', 17 | 'Widgets/Switcher.vala', 18 | 'Widgets/SearchView.vala', 19 | 'Widgets/SearchItem.vala', 20 | 'Widgets/Sidebar.vala', 21 | 'Widgets/CategoryView.vala', 22 | #'Widgets/CompositedWindow.vala', 23 | #'Widgets/PageChecker.vala', 24 | 'Widgets/StaredView.vala', 25 | #'Widgets/ActionsView.vala', 26 | asresources 27 | ] 28 | 29 | dependencies = [ 30 | glib_dep, 31 | gobject_dep, 32 | gio_dep, 33 | gio_unix_dep, 34 | gee_dep, 35 | gtk_dep, 36 | json_glib_dep, 37 | libgnome_menu_dep, 38 | libsoup_dep, 39 | plank_dep, 40 | unity_dep, 41 | synapse_core_dep, 42 | synapse_plugins_dep, 43 | posix_dep, 44 | meson.get_compiler('vala').find_library('config', dirs: join_paths(meson.source_root(), 'vapi')) 45 | ] 46 | 47 | executable( 48 | meson.project_name(), 49 | sources, 50 | dependencies: dependencies, 51 | c_args : '-DGMENU_I_KNOW_THIS_IS_UNSTABLE', 52 | vala_args:['--target-glib=2.38', '--gresources=' + meson.source_root () + '/data/icons.gresource.xml'], 53 | install: true 54 | ) 55 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: panther-launcher 3 | Source: 4 | 5 | Files: * 6 | Copyright: 7 | 8 | License: GPL-3.0+ 9 | 10 | Files: debian/* 11 | Copyright: 2017 Nick Wilkins 12 | License: GPL-3.0+ 13 | 14 | License: GPL-3.0+ 15 | This program is free software: you can redistribute it and/or modify 16 | it under the terms of the GNU General Public License as published by 17 | the Free Software Foundation, either version 3 of the License, or 18 | (at your option) any later version. 19 | . 20 | This package is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | . 25 | You should have received a copy of the GNU General Public License 26 | along with this program. If not, see . 27 | . 28 | On Debian systems, the complete text of the GNU General 29 | Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". 30 | 31 | # Please also look if there are files or directories which have a 32 | # different copyright/license attached and list them here. 33 | # Please avoid picking licenses with terms that are more restrictive than the 34 | # packaged work, as it may make Debian's contributions unacceptable upstream. 35 | -------------------------------------------------------------------------------- /lib/synapse-core/meson.build: -------------------------------------------------------------------------------- 1 | synapse_core_sources = [ 2 | 'common-actions.vala', 3 | 'config-service.vala', 4 | 'data-sink.vala', 5 | 'dbus-service.vala', 6 | 'desktop-file-service.vala', 7 | 'match.vala', 8 | 'plugin.vala', 9 | 'query.vala', 10 | 'relevancy-service.vala', 11 | 'result-set.vala', 12 | 'utils.vala', 13 | 'volume-service.vala' 14 | ] 15 | 16 | # 17 | # Configuration file 18 | # 19 | config_data = configuration_data() 20 | config_data.set('DATADIR', join_paths(get_option('prefix'), get_option('libdir'), 'wingpanel')) 21 | config_data.set('PKGDATADIR', join_paths(get_option('prefix'), get_option('libdir'), 'wingpanel')) 22 | config_data.set('GETTEXT_PACKAGE', meson.project_name()) 23 | config_data.set('RELEASE_NAME', 'Donatello') 24 | config_data.set('VERSION', meson.project_version()) 25 | config_data.set('VERSION_INFO', 'Release') 26 | 27 | config_file = configure_file( 28 | input: 'config.h.in', 29 | output: 'config.h', 30 | configuration: config_data 31 | ) 32 | 33 | synapse_core_deps = [ 34 | glib_dep, 35 | gio_unix_dep, 36 | json_glib_dep, 37 | gee_dep, 38 | gtk_dep 39 | ] 40 | 41 | synapse_core = static_library('synapse-core', 42 | synapse_core_sources, 43 | config_file, 44 | dependencies: synapse_core_deps, 45 | c_args : '-DGMENU_I_KNOW_THIS_IS_UNSTABLE', 46 | install : true) 47 | 48 | synapse_core_dep = declare_dependency( 49 | link_with: synapse_core, 50 | include_directories: include_directories('.') 51 | ) -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'launchy',['vala', 'c'] 3 | ) 4 | 5 | i18n = import('i18n') 6 | gnome = import('gnome') 7 | 8 | add_project_arguments( 9 | '-DGETTEXT_PACKAGE="@0@"'.format(meson.project_name()), 10 | language: 'c' 11 | ) 12 | 13 | vala_version_required = '0.26.0' 14 | vala = meson.get_compiler('vala') 15 | if not vala.version().version_compare('>= @0@'.format(vala_version_required)) 16 | error('Valac >= @0@ required!'.format(vala_version_required)) 17 | endif 18 | 19 | asresources = gnome.compile_resources( 20 | 'as-resources', 'data/icons.gresource.xml', 21 | source_dir: 'data', 22 | c_name: 'as' 23 | ) 24 | 25 | glib_dep = dependency('glib-2.0') 26 | gee_dep = dependency('gee-0.8') 27 | gio_dep = dependency('gio-2.0') 28 | gtk_dep = dependency('gtk+-3.0') 29 | gobject_dep = dependency('gobject-2.0') 30 | libsoup_dep = dependency('libsoup-2.4') 31 | gio_unix_dep = dependency('gio-unix-2.0') 32 | json_glib_dep = dependency('json-glib-1.0') 33 | libgnome_menu_dep = dependency('libgnome-menu-3.0') 34 | posix_dep = vala.find_library('posix') 35 | 36 | unity_dep = [] 37 | plank_dep = [] 38 | 39 | #if get_option('with-unity') 40 | # unity_dep = dependency('unity', version: '>=4.0.0') 41 | # add_project_arguments('--define=HAVE_UNITY', language: 'vala') 42 | 43 | plank_dep = dependency('plank') 44 | if plank_dep.version().version_compare('>=0.10.9') 45 | add_project_arguments('--define=HAS_PLANK_0_11', language: 'vala') 46 | endif 47 | if plank_dep.version().version_compare('>=0.9.0') 48 | add_project_arguments('--define=HAS_PLANK', language: 'vala') 49 | endif 50 | #endif 51 | 52 | subdir('data') 53 | subdir('lib') 54 | subdir('src') 55 | 56 | meson.add_install_script('meson/post_install.py') -------------------------------------------------------------------------------- /data/icons/panther-view-list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | image/svg+xml 8 | 9 | Paper Symbolic Icon Theme 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Paper Symbolic Icon Theme 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /lib/synapse-core/plugin.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Michal Hruby 3 | * 2017 elementary LLC. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public 16 | * License along with this program; if not, write to the 17 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 | * Boston, MA 02110-1301 USA 19 | * 20 | * Authored by: Michal Hruby 21 | */ 22 | 23 | namespace Synapse { 24 | public interface Activatable : Object { 25 | // this property will eventually go away 26 | public abstract bool enabled { get; set; default = true; } 27 | 28 | public abstract void activate (); 29 | public abstract void deactivate (); 30 | } 31 | 32 | public interface Configurable : Object { 33 | public abstract Gtk.Widget create_config_widget (); 34 | } 35 | 36 | public interface ItemProvider : Activatable { 37 | public abstract async ResultSet? search (Query query) throws SearchError; 38 | public virtual bool handles_query (Query query) { 39 | return true; 40 | } 41 | 42 | public virtual bool handles_empty_query () { 43 | return false; 44 | } 45 | } 46 | 47 | public interface ActionProvider : Activatable { 48 | public abstract ResultSet? find_for_match (ref Query query, Match match); 49 | public virtual bool handles_unknown () { 50 | return false; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Backend/Plank.vala: -------------------------------------------------------------------------------- 1 | using Plank; 2 | 3 | namespace Launcher 4 | { 5 | public class TestClient : Object, Plank.UnityClient 6 | { 7 | public void remove_launcher_entry (string sender_name) 8 | { 9 | print ("Client '%s' was terminated\n", sender_name); 10 | } 11 | 12 | public void update_launcher_entry (string sender_name, GLib.Variant parameters, bool is_retry = false) 13 | { 14 | print ("Client '%s' requests an update\n", sender_name); 15 | 16 | // Decode and process the given "paramaters" argument 17 | string app_uri; 18 | VariantIter prop_iter; 19 | parameters.get ("(sa{sv})", out app_uri, out prop_iter); 20 | 21 | print ("=> '%s'\n %s\n", app_uri, decode_payload (prop_iter)); 22 | } 23 | 24 | static string decode_payload (VariantIter prop_iter) 25 | { 26 | var result = new StringBuilder (); 27 | 28 | string prop_key; 29 | Variant prop_value; 30 | 31 | while (prop_iter.next ("{sv}", out prop_key, out prop_value)) { 32 | if (prop_key == "count") { 33 | var val = prop_value.get_int64 (); 34 | result.append (("count = %" + int64.FORMAT + "; ").printf (val)); 35 | } else if (prop_key == "count-visible") { 36 | var val = prop_value.get_boolean (); 37 | result.append ("count-visible = %s; ".printf (val ? "true" : "false")); 38 | } else if (prop_key == "progress") { 39 | var val = prop_value.get_double (); 40 | result.append ("progress = %f; ".printf (val)); 41 | } else if (prop_key == "progress-visible") { 42 | var val = prop_value.get_boolean (); 43 | result.append ("progress-visible = %s; ".printf (val ? "true" : "false")); 44 | } else if (prop_key == "urgent") { 45 | var val = prop_value.get_boolean (); 46 | result.append ("urgent = %s; ".printf (val ? "true" : "false")); 47 | #if HAVE_DBUSMENU 48 | } else if (prop_key == "quicklist") { 49 | /* The value is the object path of the dbusmenu */ 50 | unowned string dbus_path = prop_value.get_string (); 51 | result.append ("quicklist = %s; ".printf (dbus_path)); 52 | #endif 53 | } 54 | } 55 | 56 | return (owned) result.str; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /data/org.enso.launchy.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 5 | The default number of columns 6 | Setting this value to 0 will automatically set the number of columns to the best value, based on the screen size. 7 | 8 | 9 | 3 10 | The default number of rows 11 | Setting this value to 0 will automatically set the number of rows to the best value, based on the screen size. 12 | 13 | 14 | 64 15 | The default icon size of apps 16 | This value manages the size of the icons in Panther. 17 | 18 | 19 | 0.0 20 | The default font size for apps 21 | This value manages the size of the fonts in Panther. If it is zero, it will use the system value. 22 | 23 | 24 | true 25 | Show the category switcher or not 26 | This value handles the category switcher. 27 | 28 | 29 | true 30 | Show the category view by default 31 | Save the current view of the launcher. 32 | 33 | 34 | false 35 | Show the launcher at the top 36 | Show the launcher at the top. 37 | 38 | 39 | "" 40 | The last resolution of the screen 41 | This value makes Panther able to adapt to each screen. 42 | 43 | 44 | [] 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /data/icons/48/launchy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 14 | 16 | 17 | 18 | 24 | 25 | 26 | 27 | 28 | 34 | 35 | 36 | 38 | 39 | -------------------------------------------------------------------------------- /data/icons/32/launchy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 14 | 16 | 17 | 18 | 24 | 25 | 26 | 27 | 28 | 34 | 35 | 36 | 38 | 39 | -------------------------------------------------------------------------------- /data/icons/64/launchy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 14 | 16 | 17 | 18 | 24 | 25 | 26 | 27 | 28 | 34 | 35 | 36 | 38 | 39 | -------------------------------------------------------------------------------- /src/Backend/DBusService.vala: -------------------------------------------------------------------------------- 1 | // -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- 2 | // 3 | // Copyright (C) 2012 Panther Developers 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | using Gtk; 19 | 20 | public class Launcher.DBusService : Object { 21 | 22 | private Service? service = null; 23 | 24 | public DBusService (LaunchyView view) { 25 | // Own bus name 26 | // try to register service name in session bus 27 | Bus.own_name (BusType.SESSION, 28 | "org.enso.launchy", 29 | BusNameOwnerFlags.NONE, 30 | (conn) => { on_bus_aquired (conn, view); }, 31 | name_acquired_handler, 32 | () => { critical ("Could not aquire service name"); }); 33 | 34 | } 35 | 36 | private void on_bus_aquired (DBusConnection connection, LaunchyView view) { 37 | try { 38 | // start service and register it as dbus object 39 | service = new Service (view); 40 | connection.register_object ("/org/enso/launchy", service); 41 | } catch (IOError e) { 42 | critical ("Could not register service: %s", e.message); 43 | return_if_reached (); 44 | } 45 | } 46 | 47 | private void name_acquired_handler (DBusConnection connection, string name) { 48 | message ("Service registration suceeded"); 49 | return_if_fail (service != null); 50 | // Emit initial state 51 | service.on_view_visibility_change (); 52 | } 53 | } 54 | 55 | [DBus (name = "org.enso.launchy")] 56 | public class Service : Object { 57 | public signal void visibility_changed (bool launcher_visible); 58 | private Gtk.Window? view = null; 59 | 60 | public Service (Gtk.Window view) { 61 | this.view = view; 62 | view.show.connect (on_view_visibility_change); 63 | view.hide.connect (on_view_visibility_change); 64 | } 65 | 66 | internal void on_view_visibility_change () { 67 | this.visibility_changed (view.visible); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /data/icons/panther-icon-index.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 14 | 15 | 17 | image/svg+xml 18 | 20 | elementary Symbolic Icon Theme 21 | 22 | 23 | 24 | elementary Symbolic Icon Theme 26 | 28 | 31 | 35 | 36 | 39 | 43 | 44 | 45 | 50 | 54 | 58 | 61 | 65 | 69 | 73 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /lib/synapse-core/relevancy-service.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Michal Hruby 3 | * 2017 elementary LLC. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public 16 | * License along with this program; if not, write to the 17 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 | * Boston, MA 02110-1301 USA 19 | * 20 | * Authored by: Michal Hruby 21 | */ 22 | 23 | namespace Synapse { 24 | public interface RelevancyBackend: Object { 25 | public abstract float get_application_popularity (string desktop_id); 26 | public abstract float get_uri_popularity (string uri); 27 | 28 | public abstract void application_launched (AppInfo app_info); 29 | } 30 | 31 | public class RelevancyService : GLib.Object { 32 | // singleton that can be easily destroyed 33 | private static unowned RelevancyService? instance; 34 | public static RelevancyService get_default () { 35 | return instance ?? new RelevancyService (); 36 | } 37 | 38 | private RelevancyService () { } 39 | 40 | ~RelevancyService () { } 41 | 42 | construct { 43 | instance = this; 44 | this.add_weak_pointer (&instance); 45 | 46 | initialize_relevancy_backend (); 47 | } 48 | 49 | private RelevancyBackend backend; 50 | 51 | private void initialize_relevancy_backend () { 52 | #if HAVE_ZEITGEIST 53 | backend = new ZeitgeistRelevancyBackend (); 54 | #else 55 | backend = null; 56 | #endif 57 | } 58 | 59 | public float get_application_popularity (string desktop_id) { 60 | if (backend == null) { 61 | return 0.0f; 62 | } 63 | return backend.get_application_popularity (desktop_id); 64 | } 65 | 66 | public float get_uri_popularity (string uri) { 67 | if (backend == null) { 68 | return 0.0f; 69 | } 70 | return backend.get_uri_popularity (uri); 71 | } 72 | 73 | public void application_launched (AppInfo app_info) { 74 | Utils.Logger.debug (this, "application launched"); 75 | if (backend == null) { 76 | return; 77 | } 78 | backend.application_launched (app_info); 79 | } 80 | 81 | public static int compute_relevancy (int base_relevancy, float modifier) { 82 | // FIXME: let's experiment here 83 | // the other idea is to use base_relevancy * (1.0f + modifier) 84 | int relevancy = (int) (base_relevancy + modifier * Match.Score.INCREMENT_LARGE * 2); 85 | //int relevancy = base_relevancy + (int) (modifier * Match.Score.HIGHEST); 86 | return relevancy; 87 | // FIXME: this clamping should be done, but it screws up the popularity 88 | // for very popular items with high match score 89 | //return int.min (relevancy, Match.Score.HIGHEST); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/synapse-core/result-set.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Michal Hruby 3 | * 2017 elementary LLC. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public 16 | * License along with this program; if not, write to the 17 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 | * Boston, MA 02110-1301 USA 19 | * 20 | * Authored by: Michal Hruby 21 | */ 22 | 23 | namespace Synapse { 24 | public class ResultSet : Object, Gee.Traversable, Gee.Iterable > { 25 | protected Gee.Map matches; 26 | protected Gee.Set uris; 27 | 28 | public ResultSet () { 29 | Object (); 30 | } 31 | 32 | construct { 33 | matches = new Gee.HashMap (); 34 | // Match.uri is not owned, so we can optimize here 35 | uris = new Gee.HashSet (); 36 | } 37 | 38 | public Type element_type { get { return matches.element_type; } } 39 | 40 | public int size { get { return matches.size; } } 41 | 42 | public Gee.Set keys { owned get { return matches.keys; } } 43 | 44 | public Gee.Set> entries { owned get { return matches.entries; } } 45 | 46 | public Gee.Iterator> iterator () { return matches.iterator (); } 47 | 48 | public bool foreach (Gee.ForallFunc func) { return matches.keys.foreach (func); } 49 | 50 | public void add (Match match, int relevancy) { 51 | matches.set (match, relevancy); 52 | 53 | if (match is UriMatch) { 54 | unowned string uri = (match as UriMatch).uri; 55 | if (uri != null && uri != "") { 56 | uris.add (uri); 57 | } 58 | } 59 | } 60 | 61 | public void add_all (ResultSet? rs) { 62 | if (rs == null) { 63 | return; 64 | } 65 | matches.set_all (rs.matches); 66 | uris.add_all (rs.uris); 67 | } 68 | 69 | public bool contains_uri (string uri) { 70 | return uri in uris; 71 | } 72 | 73 | public Gee.List get_sorted_list () { 74 | var l = new Gee.ArrayList> (); 75 | l.add_all (matches.entries); 76 | 77 | l.sort ((a, b) => { 78 | unowned Gee.Map.Entry e1 = (Gee.Map.Entry) a; 79 | unowned Gee.Map.Entry e2 = (Gee.Map.Entry) b; 80 | int relevancy_delta = e2.value - e1.value; 81 | if (relevancy_delta != 0) { 82 | return relevancy_delta; 83 | } 84 | // FIXME: utf8 compare! 85 | else return e1.key.title.ascii_casecmp (e2.key.title); 86 | }); 87 | 88 | var sorted_list = new Gee.ArrayList (); 89 | foreach (Gee.Map.Entry m in l) { 90 | sorted_list.add (m.key); 91 | } 92 | 93 | return sorted_list; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Settings.vala: -------------------------------------------------------------------------------- 1 | // -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- 2 | // 3 | // Copyright (C) 2011-2012 Giulio Collura 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | 19 | using Posix; 20 | 21 | namespace Launcher { 22 | 23 | public class Settings : Object { 24 | 25 | public int columns; 26 | public int rows; 27 | public int columns_int { get; set; } 28 | public int rows_int { get; set; } 29 | public double font_size { get; set; } 30 | 31 | public int icon_size { get; set; } 32 | public bool show_category_filter { get; set; } 33 | public bool use_category { get; set; } 34 | public string screen_resolution { get; set; } 35 | public string resolution { get; set; } 36 | public bool show_at_top { get; set; } 37 | public int[] window_position { get; set; } 38 | 39 | private GLib.Settings panther_settings; 40 | 41 | public signal void columns_changed(); 42 | public signal void rows_changed(); 43 | public signal void show_at_changed(); 44 | 45 | public Settings () { 46 | this.panther_settings = new GLib.Settings("org.enso.launchy"); 47 | this.panther_settings.bind("rows",this,"rows_int",SettingsBindFlags.DEFAULT); 48 | this.panther_settings.bind("columns",this,"columns_int",SettingsBindFlags.DEFAULT); 49 | this.panther_settings.bind("icon-size",this,"icon_size",SettingsBindFlags.DEFAULT); 50 | this.panther_settings.bind("font-size",this,"font_size",SettingsBindFlags.DEFAULT); 51 | this.panther_settings.bind("show-category-filter",this,"show_category_filter",SettingsBindFlags.DEFAULT); 52 | this.panther_settings.bind("use-category",this,"use_category",SettingsBindFlags.DEFAULT); 53 | this.panther_settings.bind("screen-resolution",this,"screen_resolution",SettingsBindFlags.DEFAULT); 54 | this.panther_settings.bind("show-at-top",this,"show_at_top",SettingsBindFlags.DEFAULT); 55 | 56 | //this.panther_settings.bind("favourite",this,"favourite",SettingsBindFlags.DEFAULT); 57 | 58 | this.panther_settings.changed.connect((key) => { 59 | if (key == "rows") { 60 | this.rows = this.rows_int; 61 | this.rows_changed(); 62 | } 63 | if (key == "columns") { 64 | this.columns = this.columns_int; 65 | this.columns_changed(); 66 | } 67 | if (key == "show-at-top") { 68 | this.show_at_changed(); 69 | } 70 | if (key == "icon-size") { 71 | Posix.exit(0); 72 | } 73 | if (key == "font-size") { 74 | Posix.exit(0); 75 | } 76 | }); 77 | } 78 | 79 | public Variant get_window_positions () 80 | { 81 | //this.panther_settings.bind("window-position",this,"window_position",SettingsBindFlags.DEFAULT); 82 | return this.panther_settings.get_value("window-position"); 83 | } 84 | 85 | public void set_window_positions (int x, int y) 86 | { 87 | //this.panther_settings.bind("window-position",this,"window_position",SettingsBindFlags.DEFAULT); 88 | this.panther_settings.set_value ("window-position", new int[] { x, y }); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Widgets/Sidebar.vala: -------------------------------------------------------------------------------- 1 | // -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- 2 | // 3 | // Copyright (C) 2011-2012 Giulio Collura 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | 19 | using Gtk; 20 | 21 | public class Launcher.Widgets.Sidebar : Gtk.TreeView { 22 | 23 | private Gtk.TreeStore store; 24 | 25 | private Gtk.TreeIter entry_iter; 26 | 27 | //private UserView user_view; 28 | 29 | public int cat_size { 30 | get { 31 | return store.iter_n_children (null); 32 | } 33 | } 34 | 35 | private int _selected = 0; 36 | public int selected { 37 | get { 38 | return _selected; 39 | } 40 | set { 41 | if (value >= 0 && value < cat_size) { 42 | select_nth (value); 43 | _selected = value; 44 | } 45 | } 46 | } 47 | 48 | private enum Columns { 49 | INT, 50 | TEXT, 51 | N_COLUMNS 52 | } 53 | 54 | public signal void selection_changed (string entry_name, int nth); 55 | 56 | public Sidebar () { 57 | 58 | store = new Gtk.TreeStore (Columns.N_COLUMNS, typeof (int), typeof (string)); 59 | store.set_sort_column_id (1, Gtk.SortType.ASCENDING); 60 | set_model (store); 61 | 62 | set_headers_visible (false); 63 | set_show_expanders (false); 64 | //set_level_indentation (8); 65 | 66 | hexpand = true; 67 | get_style_context ().add_class ("sidebar"); 68 | 69 | var cell = new Gtk.CellRendererText (); 70 | cell.xpad = Pixels.PADDING; 71 | cell.ypad = 5; 72 | 73 | insert_column_with_attributes (-1, "Filters", cell, "markup", Columns.TEXT); 74 | 75 | get_selection ().set_mode (Gtk.SelectionMode.SINGLE); 76 | get_selection ().changed.connect (selection_change); 77 | 78 | } 79 | 80 | public void add_category (string entry_name) { 81 | store.append (out entry_iter, null); 82 | store.set (entry_iter, Columns.INT, cat_size - 1, Columns.TEXT, Markup.escape_text (entry_name), -1); 83 | expand_all (); 84 | } 85 | 86 | public void clear () { 87 | store.clear (); 88 | } 89 | 90 | public void selection_change () { 91 | 92 | Gtk.TreeModel model; 93 | Gtk.TreeIter sel_iter; 94 | string name; 95 | int nth; 96 | 97 | if (get_selection ().get_selected (out model, out sel_iter)) { 98 | store.get (sel_iter, Columns.INT, out nth, Columns.TEXT, out name); 99 | _selected = nth; 100 | selection_changed (name, nth); 101 | } 102 | 103 | } 104 | 105 | public bool select_nth (int nth) { 106 | 107 | Gtk.TreeIter iter; 108 | 109 | if (nth < cat_size) 110 | store.iter_nth_child (out iter, null, nth); 111 | else 112 | return false; 113 | 114 | get_selection ().select_iter (iter); 115 | return true; 116 | 117 | } 118 | 119 | protected override bool scroll_event (Gdk.EventScroll event) { 120 | 121 | switch (event.direction.to_string ()) { 122 | case "GDK_SCROLL_UP": 123 | case "GDK_SCROLL_LEFT": 124 | selected--; 125 | break; 126 | case "GDK_SCROLL_DOWN": 127 | case "GDK_SCROLL_RIGHT": 128 | selected++; 129 | break; 130 | 131 | } 132 | 133 | return false; 134 | 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /lib/synapse-plugins/web-search.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 David Hewitt 3 | * 2017 elementary LLC. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public 16 | * License along with this program; if not, write to the 17 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 | * Boston, MA 02110-1301 USA 19 | * 20 | * Authored by: David Hewitt 21 | */ 22 | 23 | namespace Synapse { 24 | public class WebSearchPlugin: Object, Activatable, ItemProvider { 25 | 26 | public bool enabled { get; set; default = true; } 27 | 28 | public void activate () { } 29 | 30 | public void deactivate () { } 31 | 32 | public class Result : Object, Match { 33 | // from Match interface 34 | public string title { get; construct set; } 35 | public string description { get; set; } 36 | public string icon_name { get; construct set; } 37 | public bool has_thumbnail { get; construct set; } 38 | public string thumbnail_path { get; construct set; } 39 | public MatchType match_type { get; construct set; } 40 | public string query_template { get; construct set; } 41 | 42 | public int default_relevancy { get; set; default = 0; } 43 | 44 | private AppInfo? appinfo; 45 | private string search_term; 46 | 47 | public Result (string search) { 48 | search_term = search; 49 | string _icon_name = "applications-internet"; 50 | 51 | appinfo = AppInfo.get_default_for_type ("x-scheme-handler/http", false); 52 | if (appinfo != null) { 53 | _title = _("Search %s with %s").printf (search_term, "Ecosia"); 54 | _icon_name = appinfo.get_icon ().to_string (); 55 | } 56 | 57 | this.title = _title; 58 | this.icon_name = _icon_name; 59 | this.description = _("Open this query in default browser"); 60 | this.has_thumbnail = false; 61 | this.match_type = MatchType.ACTION; 62 | } 63 | 64 | public void execute (Match? match) { 65 | if (appinfo == null) { 66 | return; 67 | } 68 | 69 | var list = new List (); 70 | list.append ("https://www.ecosia.org/search?q="+search_term); 71 | try { 72 | appinfo.launch_uris (list, null); 73 | } catch (Error e) { 74 | warning ("%s\n", e.message); 75 | } 76 | } 77 | } 78 | 79 | static void register_plugin () { 80 | DataSink.PluginRegistry.get_default ().register_plugin (typeof (WebSearchPlugin), 81 | _("WebSearch"), 82 | _("Search the web for result"), 83 | "applications-internet", 84 | register_plugin); 85 | } 86 | 87 | static construct { 88 | register_plugin (); 89 | } 90 | 91 | public bool handles_query (Query query) { 92 | return QueryFlags.TEXT in query.query_type; 93 | } 94 | 95 | public async ResultSet? search (Query query) throws SearchError { 96 | Result result = new Result (query.query_string); 97 | ResultSet results = new ResultSet (); 98 | results.add (result, Match.Score.AVERAGE); 99 | query.check_cancellable (); 100 | 101 | return results; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Widgets/SearchItem.vala: -------------------------------------------------------------------------------- 1 | // -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- 2 | // 3 | // Copyright (C) 2011-2012 Giulio Collura 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | 19 | using Gtk; 20 | 21 | namespace Launcher.Widgets { 22 | 23 | public class SearchItem : Gtk.Button { 24 | 25 | const int ICON_SIZE = 32; 26 | 27 | public Backend.App app { get; construct; } 28 | 29 | private Gtk.Label name_label; 30 | private Gtk.Image icon; 31 | 32 | private Cancellable? cancellable = null; 33 | public bool dragging = false; //prevent launching 34 | public bool action = false; 35 | 36 | public signal bool launch_app (); 37 | 38 | public SearchItem (Backend.App app, string search_term = "", bool action = false, string action_title = "") { 39 | Object (app: app); 40 | 41 | this.action = action; 42 | get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); 43 | get_style_context ().add_class ("app_button"); 44 | 45 | string markup; 46 | if (action) 47 | markup = action_title; 48 | else 49 | markup = Backend.SynapseSearch.markup_string_with_search (app.name, search_term); 50 | 51 | name_label = new Gtk.Label (markup); 52 | name_label.set_ellipsize (Pango.EllipsizeMode.END); 53 | name_label.use_markup = true; 54 | ((Gtk.Misc) name_label).xalign = 0.0f; 55 | 56 | icon = new Gtk.Image.from_pixbuf (app.load_icon (ICON_SIZE)); 57 | 58 | // load a favicon if we're an internet page 59 | var uri_match = app.match as Synapse.UriMatch; 60 | if (uri_match != null && (uri_match.uri.has_prefix ("http") || uri_match.uri.has_prefix ("https")) ) { 61 | cancellable = new Cancellable (); 62 | Backend.SynapseSearch.get_favicon_for_match.begin (uri_match, 63 | ICON_SIZE, cancellable, (obj, res) => { 64 | 65 | var pixbuf = Backend.SynapseSearch.get_favicon_for_match.end (res); 66 | if (pixbuf != null) 67 | icon.set_from_pixbuf (pixbuf); 68 | }); 69 | } 70 | 71 | var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12); 72 | box.pack_start (icon, false); 73 | box.pack_start (name_label, true); 74 | box.margin_start = 12; 75 | box.margin_top = box.margin_bottom = 3; 76 | 77 | add (box); 78 | 79 | if (!action) 80 | launch_app.connect (app.launch); 81 | 82 | var app_match = app.match as Synapse.ApplicationMatch; 83 | if (app_match != null) { 84 | Gtk.TargetEntry dnd = {"text/uri-list", 0, 0}; 85 | Gtk.drag_source_set (this, Gdk.ModifierType.BUTTON1_MASK, {dnd}, 86 | Gdk.DragAction.COPY); 87 | this.drag_begin.connect ( (ctx) => { 88 | this.dragging = true; 89 | Gtk.drag_set_icon_pixbuf (ctx, app.icon, 0, 0); 90 | }); 91 | this.drag_end.connect ( () => { 92 | this.dragging = false; 93 | }); 94 | this.drag_data_get.connect ( (ctx, sel, info, time) => { 95 | sel.set_uris ({File.new_for_path (app_match.filename).get_uri ()}); 96 | }); 97 | } 98 | 99 | } 100 | 101 | public override void destroy () { 102 | 103 | base.destroy (); 104 | 105 | if (cancellable != null) 106 | cancellable.cancel (); 107 | } 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /lib/synapse-plugins/link-plugin.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Magnus Kulke 3 | * 2017 elementary LLC. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public 16 | * License along with this program; if not, write to the 17 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 | * Boston, MA 02110-1301 USA 19 | * 20 | * Authored by: Magnus Kulke 21 | */ 22 | 23 | namespace Synapse { 24 | public class LinkPlugin: Object, Activatable, ItemProvider { 25 | public bool enabled { get; set; default = true; } 26 | 27 | public void activate () { } 28 | 29 | public void deactivate () { } 30 | 31 | public class Result : Object, Match { 32 | // from Match interface 33 | public string title { get; construct set; } 34 | public string description { get; set; } 35 | public string icon_name { get; construct set; } 36 | public bool has_thumbnail { get; construct set; } 37 | public string thumbnail_path { get; construct set; } 38 | public MatchType match_type { get; construct set; } 39 | 40 | public int default_relevancy { get; set; default = 0; } 41 | 42 | private string uri; 43 | private AppInfo? appinfo; 44 | 45 | public Result (string link) { 46 | uri = link; 47 | string _title = _("Open %s in default web browser".printf (uri)); 48 | string _icon_name = "web-browser"; 49 | 50 | appinfo = AppInfo.get_default_for_type ("x-scheme-handler/http", false); 51 | if (appinfo != null) { 52 | _title = _("Open %s in %s").printf (uri, appinfo.get_display_name ()); 53 | _icon_name = appinfo.get_icon ().to_string (); 54 | } 55 | 56 | this.title = _title; 57 | this.icon_name = _icon_name; 58 | this.description = _("Open this link in default browser"); 59 | this.has_thumbnail = false; 60 | this.match_type = MatchType.ACTION; 61 | } 62 | 63 | public void execute (Match? match) { 64 | if (appinfo == null) { 65 | return; 66 | } 67 | 68 | var list = new List (); 69 | list.append (uri); 70 | try { 71 | appinfo.launch_uris (list, null); 72 | } catch (Error e) { 73 | warning ("%s\n", e.message); 74 | } 75 | } 76 | } 77 | 78 | static void register_plugin () { 79 | DataSink.PluginRegistry.get_default ().register_plugin (typeof (LinkPlugin), 80 | _("Link"), 81 | _("Open link in default browser"), 82 | "web-browser", 83 | register_plugin); 84 | } 85 | 86 | static construct { 87 | register_plugin (); 88 | } 89 | 90 | private Regex regex; 91 | 92 | construct { 93 | try { 94 | regex = new Regex ("[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,4}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)", 95 | RegexCompileFlags.OPTIMIZE); 96 | } catch (Error e) { 97 | critical ("Error creating regexp: %s", e.message); 98 | } 99 | } 100 | 101 | public bool handles_query (Query query) { 102 | return QueryFlags.TEXT in query.query_type; 103 | } 104 | 105 | public async ResultSet? search (Query query) throws SearchError { 106 | bool matched = regex.match (query.query_string); 107 | if (matched) { 108 | Result result = new Result (query.query_string); 109 | ResultSet results = new ResultSet (); 110 | results.add (result, Match.Score.AVERAGE); 111 | query.check_cancellable (); 112 | 113 | return results; 114 | } 115 | 116 | return null; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/synapse-core/match.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Michal Hruby 3 | * 2017 elementary LLC. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public 16 | * License along with this program; if not, write to the 17 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 | * Boston, MA 02110-1301 USA 19 | * 20 | * Authored by: Michal Hruby 21 | * Alberto Aldegheri 22 | */ 23 | 24 | namespace Synapse { 25 | public enum MatchType { 26 | UNKNOWN = 0, 27 | TEXT, 28 | APPLICATION, 29 | GENERIC_URI, 30 | ACTION, 31 | SEARCH, 32 | CONTACT, 33 | INTERNET 34 | } 35 | 36 | public interface Match: Object { 37 | public enum Score { 38 | INCREMENT_MINOR = 2000, 39 | INCREMENT_SMALL = 5000, 40 | INCREMENT_MEDIUM = 10000, 41 | INCREMENT_LARGE = 20000, 42 | URI_PENALTY = 15000, 43 | 44 | POOR = 50000, 45 | BELOW_AVERAGE = 60000, 46 | AVERAGE = 70000, 47 | ABOVE_AVERAGE = 75000, 48 | GOOD = 80000, 49 | VERY_GOOD = 85000, 50 | EXCELLENT = 90000, 51 | 52 | HIGHEST = 100000 53 | } 54 | 55 | // properties 56 | public abstract string title { get; construct set; } 57 | public abstract string description { get; set; } 58 | public abstract string icon_name { get; construct set; } 59 | public abstract bool has_thumbnail { get; construct set; } 60 | public abstract string thumbnail_path { get; construct set; } 61 | public abstract MatchType match_type { get; construct set; } 62 | 63 | public virtual void execute (Match? match) { 64 | Utils.Logger.error (this, "execute () is not implemented"); 65 | } 66 | 67 | public virtual void execute_with_target (Match? source, Match? target = null) { 68 | if (target == null) { 69 | execute (source); 70 | } else { 71 | Utils.Logger.error (this, "execute () is not implemented"); 72 | } 73 | } 74 | 75 | public virtual bool needs_target () { 76 | return false; 77 | } 78 | 79 | public virtual QueryFlags target_flags () { 80 | return QueryFlags.ALL; 81 | } 82 | 83 | public signal void executed (); 84 | } 85 | 86 | public interface ApplicationMatch: Match { 87 | public abstract AppInfo? app_info { get; set; } 88 | public abstract bool needs_terminal { get; set; } 89 | public abstract string? filename { get; construct set; } 90 | } 91 | 92 | public interface UriMatch: Match { 93 | public abstract string uri { get; set; } 94 | public abstract QueryFlags file_type { get; set; } 95 | public abstract string mime_type { get; set; } 96 | } 97 | 98 | public interface ContactMatch: Match { 99 | public abstract void send_message (string message, bool present); 100 | public abstract void open_chat (); 101 | } 102 | 103 | public interface ExtendedInfo: Match { 104 | public abstract string? extended_info { get; set; } 105 | } 106 | 107 | public enum TextOrigin { 108 | UNKNOWN, 109 | CLIPBOARD 110 | } 111 | 112 | public interface TextMatch: Match { 113 | public abstract TextOrigin text_origin { get; set; } 114 | public abstract string get_text (); 115 | } 116 | 117 | public interface SearchMatch: Match, SearchProvider { 118 | public abstract Match search_source { get; set; } 119 | } 120 | 121 | public class DefaultMatch: Object, Match { 122 | public string title { get; construct set; } 123 | public string description { get; set; } 124 | public string icon_name { get; construct set; } 125 | public bool has_thumbnail { get; construct set; } 126 | public string thumbnail_path { get; construct set; } 127 | public MatchType match_type { get; construct set; } 128 | 129 | public DefaultMatch (string query_string) { 130 | Object (title: query_string, description: "", has_thumbnail: false, 131 | icon_name: "unknown", match_type: MatchType.UNKNOWN); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /lib/synapse-core/dbus-service.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Michal Hruby 3 | * 2017 elementary LLC. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public 16 | * License along with this program; if not, write to the 17 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 | * Boston, MA 02110-1301 USA 19 | * 20 | * Authored by: Michal Hruby 21 | */ 22 | 23 | namespace Synapse { 24 | [DBus (name = "org.freedesktop.DBus")] 25 | public interface FreeDesktopDBus : GLib.Object { 26 | public const string UNIQUE_NAME = "org.freedesktop.DBus"; 27 | public const string OBJECT_PATH = "/org/freedesktop/DBus"; 28 | 29 | public abstract async string[] list_queued_owners (string name) throws Error; 30 | public abstract async string[] list_names () throws Error; 31 | public abstract async string[] list_activatable_names () throws Error; 32 | public abstract async bool name_has_owner (string name) throws Error; 33 | public signal void name_owner_changed (string name, string old_owner, string new_owner); 34 | public abstract async uint32 start_service_by_name (string name, uint32 flags) throws Error; 35 | public abstract async string get_name_owner (string name) throws Error; 36 | } 37 | 38 | public class DBusService : Object { 39 | private FreeDesktopDBus proxy; 40 | private Gee.Set owned_names; 41 | private Gee.Set activatable_names; 42 | private Gee.Set system_activatable_names; 43 | 44 | private Utils.AsyncOnce init_once; 45 | 46 | // singleton that can be easily destroyed 47 | public static DBusService get_default () { 48 | return instance ?? new DBusService (); 49 | } 50 | 51 | private DBusService () { } 52 | 53 | private static unowned DBusService? instance; 54 | 55 | construct { 56 | instance = this; 57 | owned_names = new Gee.HashSet (); 58 | activatable_names = new Gee.HashSet (); 59 | system_activatable_names = new Gee.HashSet (); 60 | init_once = new Utils.AsyncOnce (); 61 | 62 | initialize.begin (); 63 | } 64 | 65 | ~DBusService () { 66 | instance = null; 67 | } 68 | 69 | private void name_owner_changed (FreeDesktopDBus sender, string name, string old_owner, string new_owner) { 70 | if (name.has_prefix (":")) { 71 | return; 72 | } 73 | 74 | if (old_owner == "") { 75 | owned_names.add (name); 76 | owner_changed (name, true); 77 | } else if (new_owner == "") { 78 | owned_names.remove (name); 79 | owner_changed (name, false); 80 | } 81 | } 82 | 83 | public signal void owner_changed (string name, bool is_owned); 84 | 85 | public bool name_has_owner (string name) { 86 | return name in owned_names; 87 | } 88 | 89 | public bool name_is_activatable (string name) { 90 | return name in activatable_names; 91 | } 92 | 93 | public bool service_is_available (string name) { 94 | return name in system_activatable_names; 95 | } 96 | 97 | public async void initialize () { 98 | if (init_once.is_initialized ()) { 99 | return; 100 | } 101 | var is_locked = yield init_once.enter (); 102 | if (!is_locked) { 103 | return; 104 | } 105 | 106 | string[] names; 107 | try { 108 | proxy = Bus.get_proxy_sync (BusType.SESSION, 109 | FreeDesktopDBus.UNIQUE_NAME, 110 | FreeDesktopDBus.OBJECT_PATH); 111 | 112 | proxy.name_owner_changed.connect (this.name_owner_changed); 113 | names = yield proxy.list_names (); 114 | foreach (unowned string name in names) { 115 | if (name.has_prefix (":")) { 116 | continue; 117 | } 118 | owned_names.add (name); 119 | } 120 | 121 | names = yield proxy.list_activatable_names (); 122 | foreach (unowned string session_act in names) { 123 | activatable_names.add (session_act); 124 | } 125 | } catch (Error err) { 126 | warning ("%s", err.message); 127 | } 128 | 129 | try { 130 | FreeDesktopDBus sys_proxy = Bus.get_proxy_sync ( 131 | BusType.SYSTEM, 132 | FreeDesktopDBus.UNIQUE_NAME, 133 | FreeDesktopDBus.OBJECT_PATH); 134 | 135 | names = yield sys_proxy.list_activatable_names (); 136 | foreach (unowned string system_act in names) { 137 | system_activatable_names.add (system_act); 138 | } 139 | } catch (Error sys_err) { 140 | warning ("%s", sys_err.message); 141 | } 142 | init_once.leave (true); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Widgets/CategoryView.vala: -------------------------------------------------------------------------------- 1 | // -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- 2 | // 3 | // Copyright (C) 2011-2012 Giulio Collura 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | 19 | using Gtk; 20 | 21 | public class Launcher.Widgets.CategoryView : Gtk.EventBox { 22 | 23 | private Gtk.Grid container; 24 | public Sidebar category_switcher; 25 | //public UserView user_view; 26 | public Gtk.Separator separator; 27 | public Widgets.Grid app_view; 28 | private LaunchyView view; 29 | 30 | private const string ALL_APPLICATIONS = _("All Applications"); 31 | private const string NEW_FILTER = _("Create a new Filter"); 32 | private const string SWITCHBOARD_CATEGORY = "switchboard"; 33 | 34 | private int current_position = 0; 35 | 36 | public Gee.HashMap category_ids = new Gee.HashMap (); 37 | 38 | public CategoryView (LaunchyView parent) { 39 | view = parent; 40 | set_visible_window (false); 41 | hexpand = true; 42 | 43 | container = new Gtk.Grid (); 44 | container.hexpand = true; 45 | container.row_homogeneous = false; 46 | container.column_homogeneous = false; 47 | container.orientation = Gtk.Orientation.HORIZONTAL; 48 | separator = new Gtk.Separator (Gtk.Orientation.VERTICAL); 49 | 50 | category_switcher = new Sidebar (); 51 | 52 | app_view = new Widgets.Grid (view.rows, view.columns); 53 | 54 | //user_view = new UserView (); 55 | 56 | container.attach (category_switcher, 0, 0, 1, 1); 57 | //container.attach (user_view, 0, 1, 1, 1); 58 | //container.add (separator); 59 | container.add (app_view); 60 | add (container); 61 | 62 | connect_events (); 63 | setup_sidebar (); 64 | } 65 | 66 | public void setup_sidebar () { 67 | var old_selected = category_switcher.selected; 68 | category_ids.clear (); 69 | category_switcher.clear (); 70 | app_view.set_size_request (-1, -1); 71 | // Fill the sidebar 72 | 73 | int n = 0; 74 | //category_switcher.add_category (GLib.dgettext ("gnome-menus-3.0", "★Saved").dup ()); 75 | //category_switcher.add_category (GLib.dgettext ("gnome-menus-3.0", "All").dup ()); 76 | 77 | foreach (string cat_name in view.apps.keys) { 78 | if (cat_name == SWITCHBOARD_CATEGORY) 79 | continue; 80 | 81 | category_ids.set (n, cat_name); 82 | category_switcher.add_category (GLib.dgettext ("gnome-menus-3.0", cat_name).dup ()); 83 | 84 | n++; 85 | } 86 | category_switcher.show_all (); 87 | 88 | int minimum_width; 89 | category_switcher.get_preferred_width (out minimum_width, null); 90 | 91 | // Because of the different sizes of the column widget, we need to calculate if it will fit. 92 | int removing_columns = (int)((double)minimum_width / (double)Pixels.ITEM_SIZE); 93 | if (minimum_width % Pixels.ITEM_SIZE != 0) 94 | removing_columns++; 95 | 96 | int columns = view.columns - removing_columns; 97 | app_view.resize (view.rows, columns); 98 | 99 | category_switcher.selected = old_selected; 100 | } 101 | 102 | private void connect_events () { 103 | category_switcher.selection_changed.connect ((name, nth) => { 104 | view.reset_category_focus (); 105 | string category = category_ids.get (nth); 106 | 107 | /*if(category == "Saved") 108 | view.cat_saved = true; 109 | else 110 | view.cat_saved = false;*/ 111 | 112 | show_filtered_apps (category); 113 | }); 114 | } 115 | 116 | private void add_app (Backend.App app) { 117 | var app_entry = new AppEntry (app); 118 | app_entry.app_launched.connect (() => { 119 | view.hide (); 120 | print("hide10\n"); 121 | }); 122 | app_view.append (app_entry); 123 | app_view.show_all (); 124 | 125 | } 126 | 127 | public void show_filtered_apps (string category) { 128 | app_view.clear (); 129 | /*if(category=="All"){ 130 | foreach (Backend.App app in view.app_name) 131 | add_app (app); 132 | } 133 | else if(category=="Saved"){ 134 | foreach(Backend.App app in view.saved_apps) 135 | add_app (app); 136 | } 137 | else{*/ 138 | foreach (Backend.App app in view.apps[category]) 139 | add_app (app); 140 | //} 141 | 142 | current_position = 0; 143 | 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/Widgets/Switcher.vala: -------------------------------------------------------------------------------- 1 | // -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- 2 | // 3 | // Copyright (C) 2011-2012 Giulio Collura 4 | // Copyright (C) 2014 Corentin Noël 5 | // 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | // 19 | 20 | using Gtk; 21 | 22 | public class Launcher.Widgets.Switcher : Gtk.Box { 23 | 24 | const string SWITCHER_STYLE_CSS = """ 25 | .switcher { 26 | background-color: transparent; 27 | border: none; 28 | box-shadow: none; 29 | opacity: 0.4; 30 | color: #000; 31 | } 32 | 33 | .switcher:checked { 34 | background-color: transparent; 35 | border: none; 36 | box-shadow: none; 37 | opacity: 1; 38 | color: #4285F4; 39 | } 40 | """; 41 | 42 | public int size { 43 | get { 44 | return (int) buttons.size; 45 | } 46 | } 47 | 48 | private Gtk.Stack stack; 49 | private Gee.HashMap buttons; 50 | public signal void on_stack_changed (); 51 | 52 | public Switcher () { 53 | orientation = Gtk.Orientation.HORIZONTAL; 54 | spacing = 2; 55 | can_focus = false; 56 | buttons = new Gee.HashMap (null, null); 57 | pack_start (new Gtk.Grid (), true, true, 0); 58 | pack_end (new Gtk.Grid (), true, true, 0); 59 | } 60 | 61 | construct { 62 | var provider = new Gtk.CssProvider (); 63 | try { 64 | provider.load_from_data (SWITCHER_STYLE_CSS, SWITCHER_STYLE_CSS.length); 65 | Gtk.StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); 66 | } catch (Error e) { 67 | critical (e.message); 68 | } 69 | } 70 | 71 | public void set_stack (Gtk.Stack stack) { 72 | if (this.stack != null) { 73 | clear_children (); 74 | } 75 | this.stack = stack; 76 | populate_switcher (); 77 | connect_stack_signals (); 78 | update_selected (); 79 | } 80 | 81 | private void add_child (Gtk.Widget widget) { 82 | var button = new Gtk.ToggleButton (); 83 | button.image = new Gtk.Image.from_icon_name ("panther-icon-index", Gtk.IconSize.MENU); 84 | button.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); 85 | button.get_style_context ().add_class ("switcher"); 86 | button.button_release_event.connect (() => { 87 | foreach (var entry in buttons.entries) { 88 | if (entry.value == button) 89 | on_button_clicked (entry.key); 90 | entry.value.active = false; 91 | } 92 | button.active = true; 93 | return true; 94 | }); 95 | 96 | add (button); 97 | buttons.set (widget, button); 98 | if (buttons.size == 1) 99 | button.active = true; 100 | 101 | // show all children after update 102 | show_all (); 103 | } 104 | 105 | public override void show () { 106 | base.show (); 107 | if (buttons.size <= 1) 108 | hide (); 109 | } 110 | 111 | public override void show_all () { 112 | base.show_all (); 113 | if (buttons.size <= 1) 114 | hide (); 115 | } 116 | 117 | private void on_button_clicked (Gtk.Widget widget) { 118 | stack.set_visible_child (widget); 119 | on_stack_changed (); 120 | } 121 | 122 | private void populate_switcher () { 123 | foreach (var child in stack.get_children ()) { 124 | add_child (child); 125 | } 126 | } 127 | 128 | private void on_stack_child_removed (Gtk.Widget widget) { 129 | var button = buttons.get (widget); 130 | remove (button); 131 | buttons.unset (widget); 132 | } 133 | 134 | private void connect_stack_signals () { 135 | stack.add.connect_after (add_child); 136 | stack.remove.connect_after (on_stack_child_removed); 137 | } 138 | 139 | public void clear_children () { 140 | foreach (weak Gtk.Widget button in get_children ()) { 141 | button.hide (); 142 | if (button.get_parent () != null) 143 | remove (button); 144 | } 145 | } 146 | 147 | public void update_selected () { 148 | foreach (var entry in buttons.entries) { 149 | if (entry.key == stack.get_visible_child ()) { 150 | entry.value.active = true; 151 | } else { 152 | entry.value.active = false; 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Launchy.vala: -------------------------------------------------------------------------------- 1 | // -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- 2 | // 3 | // Copyright (C) 2011-2012 Giulio Collura 4 | // Copyright (C) 2015 Raster Software Vigo 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | 19 | using Gtk; 20 | using Gdk; 21 | using GLib; 22 | 23 | Launcher.Launchy app; 24 | // project version = 1.10.0 25 | 26 | public class Launcher.Launchy : Gtk.Application { 27 | 28 | public LaunchyView view = null; 29 | public static bool silent = false; 30 | public static bool command_mode = false; 31 | public static bool key_activated = false; 32 | public bool launched = false; 33 | public Gtk.ToggleButton app_button; 34 | public TestClient client; 35 | 36 | public static Settings settings { get; private set; default = null; } 37 | public static Gtk.IconTheme icon_theme { get; set; default = null; } 38 | public DBusService? dbus_service = null; 39 | 40 | private int view_width; 41 | private int view_height; 42 | 43 | construct { 44 | application_id = "org.enso.launchy"; 45 | } 46 | 47 | public int set_view_width { 48 | set { 49 | this.view_width = value; 50 | } 51 | } 52 | 53 | public int set_view_height { 54 | set { 55 | this.view_height = value; 56 | } 57 | } 58 | 59 | public Launchy () { 60 | settings = new Settings (); 61 | Pixels.ITEM_SIZE = settings.icon_size * 2; 62 | Pixels.SIDEBAR_WIDTH = Pixels.PADDING + Pixels.ITEM_SIZE - Pixels.SIDEBAR_GRID_PADDING - 1; 63 | } 64 | 65 | public bool realize_view(Cairo.Context? cr) { 66 | Gtk.Allocation alloc; 67 | this.view.get_allocation(out alloc); 68 | if ((alloc.width != this.view_width) || (alloc.height != this.view_height)) { 69 | this.view_width = alloc.width; 70 | this.view_height = alloc.height; 71 | this.view.reposition(); 72 | } 73 | return false; 74 | } 75 | 76 | protected override void activate () { 77 | if (this.get_windows () == null) { 78 | this.view_width = -1; 79 | this.view_height = -1; 80 | this.view = new LaunchyView (); 81 | this.view.set_application (this); 82 | //this.view.draw.connect_after(this.realize_view); 83 | 84 | if (dbus_service == null) 85 | this.dbus_service = new DBusService (view); 86 | 87 | if (!Launchy.silent) { 88 | this.view.show_launchy (); 89 | } 90 | } else { 91 | if (this.view.visible && !Launchy.silent) { 92 | this.view.hide (); 93 | } else { 94 | this.view.show_launchy (); 95 | } 96 | } 97 | Launchy.silent = false; 98 | } 99 | 100 | const OptionEntry[] entries = { 101 | { "silent", 's', 0, OptionArg.NONE, ref silent, "Launch Launchy as a background process without it appearing visually.", null }, 102 | { "key-activated", 's', 0, OptionArg.NONE, ref key_activated, "Launch Launchy as a background process without it appearing visually.", null }, 103 | { "command-mode", 'c', 0, OptionArg.NONE, ref command_mode, "This feature is not implemented yet. When it is, description will be changed.", null }, 104 | { null } 105 | }; 106 | 107 | public static int main (string[] args) { 108 | Gtk.init (ref args); 109 | /* Intl.bindtextdomain(Constants.GETTEXT_PACKAGE, GLib.Path.build_filename(Constants.DATADIR,"locale")); 110 | Intl.textdomain(Constants.GETTEXT_PACKAGE); 111 | Intl.bind_textdomain_codeset(Constants.GETTEXT_PACKAGE, "UTF-8" );*/ 112 | 113 | if (args.length > 1) { 114 | var context = new OptionContext (""); 115 | context.add_main_entries (entries, "launchy"); 116 | context.add_group (Gtk.get_option_group (true)); 117 | 118 | try { 119 | context.parse (ref args); 120 | } catch (Error e) { 121 | print (e.message + "\n"); 122 | } 123 | 124 | } 125 | app = new Launchy (); 126 | 127 | Bus.own_name (BusType.SESSION, "org.enso.launchy.remotecontrol", BusNameOwnerFlags.NONE, on_bus_aquired, () => {}, () => {}); 128 | 129 | return app.run (args); 130 | } 131 | } 132 | 133 | void on_bus_aquired (DBusConnection conn) { 134 | try { 135 | conn.register_object ("/org/enso/launchy/remotecontrol", new RemoteControl ()); 136 | } catch (IOError e) { 137 | GLib.stderr.printf ("Could not register service\n"); 138 | } 139 | } 140 | 141 | [DBus (name = "org.enso.launchy.remotecontrol")] 142 | public class RemoteControl : GLib.Object { 143 | 144 | public int do_ping(int v) { 145 | return (v+1); 146 | } 147 | 148 | public void do_show() { 149 | print("Called from DBus\n"); 150 | app.activate(); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /data/icons/panther-bookmarks.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | image/svg+xml 12 | 13 | Paper Symbolic Icon Theme 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Paper Symbolic Icon Theme 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /lib/synapse-plugins/command-plugin.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Michal Hruby 3 | * 2017 elementary LLC. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public 16 | * License along with this program; if not, write to the 17 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 | * Boston, MA 02110-1301 USA 19 | * 20 | * Authored by: Michal Hruby 21 | */ 22 | 23 | namespace Synapse { 24 | public class CommandPlugin: Object, Activatable, ItemProvider { 25 | public bool enabled { get; set; default = true; } 26 | 27 | public void activate () { } 28 | 29 | public void deactivate () { } 30 | 31 | private class CommandObject: Object, Match, ApplicationMatch { 32 | // for Match interface 33 | public string title { get; construct set; } 34 | public string description { get; set; default = ""; } 35 | public string icon_name { get; construct set; default = ""; } 36 | public bool has_thumbnail { get; construct set; default = false; } 37 | public string thumbnail_path { get; construct set; } 38 | public MatchType match_type { get; construct set; } 39 | 40 | // for ApplicationMatch 41 | public AppInfo? app_info { get; set; default = null; } 42 | public bool needs_terminal { get; set; default = false; } 43 | public string? filename { get; construct set; default = null; } 44 | public string command { get; construct set; } 45 | 46 | public CommandObject (string cmd) { 47 | Object (title: _("Execute '%s'").printf (cmd), description: _("Run command"), command: cmd, 48 | icon_name: "application-x-executable", 49 | match_type: MatchType.APPLICATION, 50 | needs_terminal: cmd.has_prefix ("sudo ")); 51 | 52 | try { 53 | app_info = AppInfo.create_from_commandline ("sh -c \"" + cmd.replace ("\"", "\\\"") + "\"", null, 0); 54 | } catch (Error err) { 55 | warning ("%s", err.message); 56 | } 57 | } 58 | } 59 | 60 | static void register_plugin () { 61 | DataSink.PluginRegistry.get_default ().register_plugin (typeof (CommandPlugin), 62 | "Command Search", 63 | _("Find and execute arbitrary commands."), 64 | "system-run", 65 | register_plugin); 66 | } 67 | 68 | static construct { 69 | register_plugin (); 70 | } 71 | 72 | private Gee.Set past_commands; 73 | private Regex split_regex; 74 | 75 | construct { 76 | // TODO: load from configuration 77 | past_commands = new Gee.HashSet (); 78 | 79 | try { 80 | split_regex = new Regex ("\\s+", RegexCompileFlags.OPTIMIZE); 81 | } catch (RegexError err) { 82 | critical ("%s", err.message); 83 | } 84 | } 85 | 86 | private CommandObject? create_co (string exec) { 87 | // ignore results that will be returned by DesktopFilePlugin 88 | // and at the same time look for hidden and no-display desktop files, 89 | // so we can display their info (title, comment, icon) 90 | var dfs = DesktopFileService.get_default (); 91 | var df_list = dfs.get_desktop_files_for_exec (exec); 92 | DesktopFileInfo? dfi = null; 93 | 94 | foreach (var df in df_list) { 95 | if (!df.is_hidden) { 96 | return null; // will be handled by App plugin 97 | } 98 | dfi = df; 99 | } 100 | 101 | var co = new CommandObject (exec); 102 | if (dfi != null) { 103 | co.title = dfi.name; 104 | if (dfi.comment != "") { 105 | co.description = dfi.comment; 106 | } 107 | 108 | if (dfi.icon_name != null && dfi.icon_name != "") { 109 | co.icon_name = dfi.icon_name; 110 | } 111 | } 112 | 113 | return co; 114 | } 115 | 116 | private void command_executed (Match match) { 117 | CommandObject? co = match as CommandObject; 118 | if (co == null) { 119 | return; 120 | } 121 | 122 | past_commands.add (co.command); 123 | } 124 | 125 | public async ResultSet? search (Query q) throws SearchError { 126 | // we only search for applications 127 | if (!(QueryFlags.APPLICATIONS in q.query_type)) { 128 | return null; 129 | } 130 | 131 | Idle.add (search.callback); 132 | yield; 133 | 134 | var result = new ResultSet (); 135 | string stripped = q.query_string.strip (); 136 | 137 | if (stripped == "") { 138 | return null; 139 | } 140 | 141 | if (stripped.has_prefix ("~/")) { 142 | stripped = stripped.replace ("~", Environment.get_home_dir ()); 143 | } 144 | 145 | if (!(stripped in past_commands)) { 146 | foreach (var command in past_commands) { 147 | if (command.has_prefix (stripped)) { 148 | result.add (create_co (command), Match.Score.AVERAGE); 149 | } 150 | } 151 | 152 | string[] args = split_regex.split (stripped); 153 | string? valid_cmd = Environment.find_program_in_path (args[0]); 154 | 155 | if (valid_cmd != null) { 156 | // don't allow dangerous commands 157 | if (args[0] == "rm") { 158 | return null; 159 | } 160 | CommandObject? co = create_co (stripped); 161 | if (co == null) { 162 | return null; 163 | } 164 | 165 | result.add (co, Match.Score.POOR); 166 | co.executed.connect (this.command_executed); 167 | } 168 | } else { 169 | result.add (create_co (stripped), Match.Score.VERY_GOOD); 170 | } 171 | q.check_cancellable (); 172 | 173 | return result; 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /lib/synapse-plugins/calculator-plugin.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Michal Hruby 3 | * 2017 elementary LLC. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public 16 | * License along with this program; if not, write to the 17 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 | * Boston, MA 02110-1301 USA 19 | * 20 | * Authored by: Michal Hruby 21 | */ 22 | 23 | namespace Synapse { 24 | public class CalculatorPlugin: Object, Activatable, ItemProvider { 25 | public bool enabled { get; set; default = true; } 26 | 27 | public void activate () { } 28 | public void deactivate () { } 29 | 30 | private class Result: Object, Match { 31 | // from Match interface 32 | public string title { get; construct set; } 33 | public string description { get; set; } 34 | public string icon_name { get; construct set; } 35 | public bool has_thumbnail { get; construct set; } 36 | public string thumbnail_path { get; construct set; } 37 | public MatchType match_type { get; construct set; } 38 | 39 | public int default_relevancy { get; set; default = 0; } 40 | 41 | public Result (double result, string match_string) { 42 | Object (match_type: MatchType.TEXT, 43 | title: "%g".printf (result), 44 | description: "%s = %g".printf (match_string, result), 45 | has_thumbnail: false, icon_name: "accessories-calculator"); 46 | } 47 | } 48 | 49 | static void register_plugin () { 50 | DataSink.PluginRegistry.get_default ().register_plugin (typeof (CalculatorPlugin), 51 | _("Calculator"), 52 | _("Calculate basic expressions."), 53 | "accessories-calculator", 54 | register_plugin, 55 | Environment.find_program_in_path ("bc") != null, 56 | _("bc is not installed")); 57 | } 58 | 59 | static construct { 60 | register_plugin (); 61 | } 62 | 63 | private Regex regex; 64 | 65 | construct { 66 | /* The regex describes a string which *resembles* a mathematical expression. It does not 67 | check for pairs of parantheses to be used correctly and only whitespace-stripped strings 68 | will match. Basically it matches strings of the form: 69 | "paratheses_open* number (operator paratheses_open* number paratheses_close*)+" 70 | */ 71 | try { 72 | regex = new Regex ("^\\(*(-?\\d+([.,]\\d+)?)([*/+-^]\\(*(-?\\d+([.,]\\d+)?)\\)*)+$", RegexCompileFlags.OPTIMIZE); 73 | } catch (Error e) { 74 | critical ("Error creating regexp: %s", e.message); 75 | } 76 | } 77 | 78 | public bool handles_query (Query query) { 79 | return (QueryFlags.ACTIONS in query.query_type); 80 | } 81 | 82 | public async ResultSet? search (Query query) throws SearchError { 83 | string input = query.query_string.replace (" ", "").replace (",", "."); 84 | bool matched = regex.match (input); 85 | 86 | if (!matched && input.length > 1) { 87 | input = input[0 : input.length - 1]; 88 | matched = regex.match (input); 89 | } 90 | if (matched) { 91 | Pid pid; 92 | int read_fd, write_fd; 93 | string[] argv = {"bc", "-l"}; 94 | string? solution = null; 95 | 96 | try { 97 | Process.spawn_async_with_pipes (null, argv, null, 98 | SpawnFlags.SEARCH_PATH, 99 | null, out pid, out write_fd, out read_fd); 100 | UnixInputStream read_stream = new UnixInputStream (read_fd, true); 101 | DataInputStream bc_output = new DataInputStream (read_stream); 102 | 103 | UnixOutputStream write_stream = new UnixOutputStream (write_fd, true); 104 | DataOutputStream bc_input = new DataOutputStream (write_stream); 105 | 106 | bc_input.put_string (input + "\n", query.cancellable); 107 | yield bc_input.close_async (Priority.DEFAULT, query.cancellable); 108 | solution = yield bc_output.read_line_async (Priority.DEFAULT_IDLE, query.cancellable); 109 | 110 | if (solution != null) { 111 | double d = double.parse (solution); 112 | Result result = new Result (d, query.query_string); 113 | ResultSet results = new ResultSet (); 114 | results.add (result, Match.Score.AVERAGE); 115 | query.check_cancellable (); 116 | 117 | return results; 118 | } 119 | } catch (Error err) { 120 | if (!query.is_cancelled ()) { 121 | warning ("%s", err.message); 122 | } 123 | } 124 | } 125 | 126 | query.check_cancellable (); 127 | return null; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Backend/AppSystem.vala: -------------------------------------------------------------------------------- 1 | // -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- 2 | // 3 | // Copyright (C) 2011-2012 Panther Developers 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | 19 | using Gtk; 20 | 21 | public class Launcher.Backend.AppSystem : Object { 22 | 23 | const string GCC_PANEL_CATEGORY = "X-GNOME-Settings-Panel"; 24 | 25 | private Gee.ArrayList categories = null; 26 | private Gee.HashMap> apps = null; 27 | private GMenu.Tree apps_menu = null; 28 | 29 | #if HAVE_ZEITGEIST 30 | private RelevancyService rl_service; 31 | #endif 32 | 33 | public signal void changed (); 34 | 35 | construct { 36 | #if HAVE_ZEITGEIST 37 | rl_service = new RelevancyService (); 38 | rl_service.update_complete.connect (update_popularity); 39 | #endif 40 | 41 | apps_menu = new GMenu.Tree ("panther-applications.menu", GMenu.TreeFlags.INCLUDE_EXCLUDED | GMenu.TreeFlags.SORT_DISPLAY_NAME); 42 | apps_menu.changed.connect (update_app_system); 43 | 44 | apps = new Gee.HashMap> (); 45 | categories = new Gee.ArrayList (); 46 | 47 | update_app_system (); 48 | } 49 | 50 | public void update_app_system () { 51 | debug ("Updating Applications menu tree..."); 52 | #if HAVE_ZEITGEIST 53 | rl_service.refresh_popularity (); 54 | #endif 55 | try { 56 | apps_menu.load_sync (); 57 | } catch (Error e) { 58 | warning (e.message); 59 | } 60 | 61 | update_categories_index (); 62 | update_apps (); 63 | //update_saved_apps (); 64 | 65 | changed (); 66 | } 67 | 68 | private void update_categories_index () { 69 | categories.clear (); 70 | 71 | var iter = apps_menu.get_root_directory ().iter (); 72 | GMenu.TreeItemType type; 73 | while ((type = iter.next ()) != GMenu.TreeItemType.INVALID) { 74 | if (type == GMenu.TreeItemType.DIRECTORY) { 75 | var dir = iter.get_directory (); 76 | if (!dir.get_is_nodisplay ()) 77 | categories.add (dir); 78 | } 79 | } 80 | } 81 | 82 | #if HAVE_ZEITGEIST 83 | private void update_popularity () { 84 | foreach (Gee.ArrayList category in apps.values) 85 | foreach (App app in category) 86 | app.popularity = rl_service.get_app_popularity (app.desktop_id); 87 | } 88 | #endif 89 | 90 | private void update_apps () { 91 | apps.clear (); 92 | foreach (var cat in categories) 93 | apps.set (cat.get_name (), get_apps_by_category (cat)); 94 | } 95 | 96 | public Gee.ArrayList get_categories () { 97 | return categories; 98 | } 99 | 100 | public Gee.ArrayList get_saved_apps () 101 | { 102 | var result = new Gee.ArrayList (); 103 | var config = File.new_for_path (Environment.get_user_config_dir ()); 104 | config = config.get_child ("panther"); 105 | File source_dir = config.get_child ("saved"); 106 | 107 | if (source_dir.query_exists ()) { 108 | try { 109 | var enumerator = source_dir.enumerate_children (FileAttribute.STANDARD_NAME + "," + FileAttribute.STANDARD_IS_HIDDEN, 0); 110 | FileInfo info; 111 | while ((info = enumerator.next_file ()) != null) { 112 | var filename = info.get_name (); 113 | 114 | if (info.get_is_hidden () || !filename.has_suffix (".saveditem")) 115 | continue; 116 | 117 | filename = filename.substring(0, filename.length - 9) + "desktop"; 118 | 119 | GMenu.TreeEntry te = apps_menu.get_entry_by_id(filename); 120 | var app = new App (te); 121 | 122 | result.add (app); 123 | } 124 | } catch (Error e) { 125 | critical ("Error loading dock elements from '%s'. (%s)", source_dir.get_path () ?? "", e.message); 126 | } 127 | } 128 | 129 | return result; 130 | } 131 | 132 | public Gee.ArrayList get_apps_by_category (GMenu.TreeDirectory category) { 133 | var app_list = new Gee.ArrayList (); 134 | var iter = category.iter (); 135 | GMenu.TreeItemType type; 136 | 137 | while ((type = iter.next ()) != GMenu.TreeItemType.INVALID) { 138 | switch (type) { 139 | case GMenu.TreeItemType.DIRECTORY: 140 | app_list.add_all (get_apps_by_category (iter.get_directory ())); 141 | break; 142 | case GMenu.TreeItemType.ENTRY: 143 | var app = new App (iter.get_entry ()); 144 | #if HAVE_ZEITGEIST 145 | app.launched.connect (rl_service.app_launched); 146 | #endif 147 | app_list.add (app); 148 | break; 149 | } 150 | } 151 | 152 | return app_list; 153 | } 154 | 155 | public Gee.HashMap> get_apps () { 156 | return apps; 157 | } 158 | 159 | public SList get_apps_by_name () { 160 | var sorted_apps = new SList (); 161 | string[] sorted_apps_execs = {}; 162 | 163 | foreach (Gee.ArrayList category in apps.values) { 164 | foreach (App app in category) { 165 | 166 | if (app.categories != null 167 | && (GCC_PANEL_CATEGORY in app.categories)) 168 | continue; 169 | 170 | 171 | if (!(app.exec in sorted_apps_execs)) { 172 | sorted_apps.insert_sorted_with_data (app, Utils.sort_apps_by_name); 173 | sorted_apps_execs += app.exec; 174 | } 175 | } 176 | } 177 | 178 | return sorted_apps; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/Widgets/StaredView.vala: -------------------------------------------------------------------------------- 1 | // -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- 2 | // 3 | // Copyright (C) 2018 Nick Wilkins 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | 19 | using Gtk; 20 | 21 | namespace Launcher.Widgets { 22 | 23 | public class StaredView : Gtk.Box { 24 | 25 | public Widgets.Switcher page_switcher; 26 | 27 | private Gtk.Stack stack; 28 | private Gtk.Grid current_grid; 29 | private Gee.HashMap grids; 30 | 31 | private uint current_row = 0; 32 | private uint current_col = 0; 33 | 34 | private Launcher.Widgets.Page page; 35 | 36 | public StaredView (int rows, int columns) { 37 | page.rows = rows; 38 | page.columns = columns; 39 | page.number = 1; 40 | var main_grid = new Gtk.Grid (); 41 | main_grid.orientation = Gtk.Orientation.VERTICAL; 42 | main_grid.row_spacing = 6; 43 | main_grid.margin_bottom = 12; 44 | stack = new Gtk.Stack (); 45 | stack.expand = true; 46 | stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT; 47 | page_switcher = new Widgets.Switcher (); 48 | page_switcher.set_stack (stack); 49 | var fake_grid = new Gtk.Grid (); 50 | fake_grid.expand = true; 51 | 52 | main_grid.add (stack); 53 | main_grid.add (fake_grid); 54 | main_grid.add (page_switcher); 55 | add (main_grid); 56 | 57 | grids = new Gee.HashMap (null, null); 58 | create_new_grid (); 59 | go_to_number (1); 60 | } 61 | 62 | private void create_new_grid () { 63 | // Grid properties 64 | current_grid = new Gtk.Grid (); 65 | current_grid.expand = true; 66 | current_grid.row_homogeneous = true; 67 | current_grid.column_homogeneous = true; 68 | current_grid.margin_start = 12; 69 | current_grid.margin_end = 12; 70 | 71 | current_grid.row_spacing = Pixels.ROW_SPACING; 72 | current_grid.column_spacing = 0; 73 | grids.set (page.number, current_grid); 74 | stack.add_titled (current_grid, page.number.to_string (), page.number.to_string ()); 75 | 76 | // Fake grids in case there are not enough apps to fill the grid 77 | for (var row = 0; row < page.rows; row++) 78 | for (var column = 0; column < page.columns; column++) 79 | current_grid.attach (new Gtk.Grid (), column, row, 1, 1); 80 | } 81 | 82 | public void append (Gtk.Widget widget) { 83 | update_position (); 84 | 85 | current_grid.attach (widget, (int)current_col, (int)current_row, 1, 1); 86 | current_col++; 87 | current_grid.show (); 88 | } 89 | 90 | private void update_position () { 91 | if (current_col == page.columns) { 92 | current_col = 0; 93 | current_row++; 94 | } 95 | 96 | if (current_row == page.rows) { 97 | page.number++; 98 | create_new_grid (); 99 | current_row = 0; 100 | } 101 | } 102 | 103 | public void clear () { 104 | foreach (Gtk.Grid grid in grids.values) { 105 | foreach (Gtk.Widget widget in grid.get_children ()) { 106 | widget.destroy (); 107 | } 108 | 109 | grid.destroy (); 110 | } 111 | 112 | grids.clear (); 113 | current_row = 0; 114 | current_col = 0; 115 | page.number = 1; 116 | create_new_grid (); 117 | stack.set_visible_child (current_grid); 118 | } 119 | 120 | public Gtk.Widget? get_child_at (int column, int row) { 121 | var col = ((int)(column/page.columns))+1; 122 | 123 | var grid = grids.get (col); 124 | if (grid != null) { 125 | return grid.get_child_at (column - (int)page.columns*(col-1), row) as Widgets.AppEntry; 126 | } else { 127 | return null; 128 | } 129 | } 130 | 131 | public int get_page_columns () { 132 | return (int) page.columns; 133 | } 134 | 135 | public int get_page_rows () { 136 | return (int) page.rows; 137 | } 138 | 139 | public int get_n_pages () { 140 | return (int) page.number; 141 | } 142 | 143 | public int get_current_page () { 144 | return int.parse (stack.get_visible_child_name ()); 145 | } 146 | 147 | public void go_to_next () { 148 | int page_number = get_current_page ()+1; 149 | if (page_number <= get_n_pages ()) 150 | stack.set_visible_child_name (page_number.to_string ()); 151 | 152 | page_switcher.update_selected (); 153 | } 154 | 155 | public void go_to_previous () { 156 | int page_number = get_current_page ()-1; 157 | if (page_number > 0) 158 | stack.set_visible_child_name (page_number.to_string ()); 159 | 160 | page_switcher.update_selected (); 161 | } 162 | 163 | public void go_to_last () { 164 | stack.set_visible_child_name (get_n_pages ().to_string ()); 165 | page_switcher.update_selected (); 166 | } 167 | 168 | public void go_to_number (int number) { 169 | stack.set_visible_child_name (number.to_string ()); 170 | page_switcher.update_selected (); 171 | } 172 | 173 | public void resize (int rows, int columns) { 174 | clear (); 175 | page.rows = rows; 176 | page.columns = columns; 177 | page.number = 1; 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /lib/synapse-core/volume-service.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Michal Hruby 3 | * 2017 elementary LLC. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public 16 | * License along with this program; if not, write to the 17 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 | * Boston, MA 02110-1301 USA 19 | * 20 | * Authored by: Michal Hruby 21 | */ 22 | 23 | namespace Synapse { 24 | public class VolumeService : GLib.Object { 25 | // singleton that can be easily destroyed 26 | private static unowned VolumeService? instance; 27 | public static VolumeService get_default () { 28 | return instance ?? new VolumeService (); 29 | } 30 | 31 | private VolumeService () { } 32 | 33 | ~VolumeService () { 34 | instance = null; 35 | } 36 | 37 | private VolumeMonitor vm; 38 | private Gee.Map volumes; 39 | 40 | construct { 41 | instance = this; 42 | volumes = new Gee.HashMap (); 43 | 44 | initialize (); 45 | } 46 | 47 | protected void initialize () { 48 | vm = VolumeMonitor.get (); 49 | 50 | vm.volume_added.connect ((volume) => { 51 | volumes[volume] = new VolumeObject (volume); 52 | }); 53 | vm.volume_removed.connect ((volume) => { 54 | volumes.unset (volume); 55 | }); 56 | vm.mount_added.connect ((mount) => { 57 | var volume = mount.get_volume (); 58 | if (volume == null) { 59 | return; 60 | } 61 | 62 | if (volume in volumes.keys) { 63 | volumes[volume].update_state (); 64 | } 65 | }); 66 | // FIXME: connect also to other signals? 67 | 68 | var volume_list = vm.get_volumes (); 69 | process_volume_list (volume_list); 70 | } 71 | 72 | private void process_volume_list (GLib.List volume_list) { 73 | foreach (unowned GLib.Volume volume in volume_list) { 74 | volumes[volume] = new VolumeObject (volume); 75 | } 76 | } 77 | 78 | public Gee.Collection get_volumes () { 79 | return volumes.values; 80 | } 81 | 82 | public string? uri_to_volume_name (string uri, out string? volume_path) { 83 | volume_path = null; 84 | var g_volumes = volumes.keys; 85 | 86 | var f = File.new_for_uri (uri); 87 | // FIXME: cache this somehow 88 | foreach (var volume in g_volumes) { 89 | File? root = volume.get_activation_root (); 90 | if (root == null) { 91 | var mount = volume.get_mount (); 92 | if (mount == null) { 93 | continue; 94 | } 95 | root = mount.get_root (); 96 | } 97 | 98 | if (f.has_prefix (root)) { 99 | volume_path = root.get_path (); 100 | return volume.get_name (); 101 | } 102 | } 103 | 104 | return null; 105 | } 106 | 107 | public class VolumeObject: Object, Match, UriMatch { 108 | public string title { get; construct set; } 109 | public string description { get; set; } 110 | public string icon_name { get; construct set; } 111 | public bool has_thumbnail { get; construct set; } 112 | public string thumbnail_path { get; construct set; } 113 | public MatchType match_type { get; construct set; } 114 | 115 | // UriMatch 116 | public string uri { get; set; } 117 | public QueryFlags file_type { get; set; } 118 | public string mime_type { get; set; } 119 | 120 | private ulong changed_signal_id; 121 | 122 | private GLib.Volume _volume; 123 | public GLib.Volume volume { 124 | get { return _volume; } 125 | set { 126 | _volume = value; 127 | title = value.get_name (); 128 | description = ""; // FIXME 129 | icon_name = value.get_icon ().to_string (); 130 | has_thumbnail = false; 131 | match_type = value.get_mount () != null ? 132 | MatchType.GENERIC_URI : MatchType.ACTION; 133 | 134 | if (match_type == MatchType.GENERIC_URI) { 135 | uri = value.get_mount ().get_root ().get_uri (); 136 | file_type = QueryFlags.PLACES; 137 | mime_type = ""; // FIXME: do we need this? 138 | } else { 139 | uri = null; 140 | } 141 | 142 | if (changed_signal_id == 0) { 143 | changed_signal_id = _volume.changed.connect (this.update_state); 144 | } 145 | 146 | Utils.Logger.debug (this, "vo[%p]: %s [%s], has_mount: %d, uri: %s", this, title, icon_name, (value.get_mount () != null ? 1 : 0), uri); 147 | } 148 | } 149 | 150 | public void update_state () { 151 | this.volume = _volume; // call setter again 152 | } 153 | 154 | public bool is_mounted () { 155 | return _volume.get_mount () != null; 156 | } 157 | 158 | public VolumeObject (GLib.Volume volume) { 159 | Object (volume: volume); 160 | } 161 | 162 | ~VolumeObject () { 163 | if (changed_signal_id != 0) { 164 | SignalHandler.disconnect (_volume, changed_signal_id); 165 | changed_signal_id = 0; 166 | } 167 | } 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/Widgets/Grid.vala: -------------------------------------------------------------------------------- 1 | // -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- 2 | // 3 | // Copyright (C) 2011-2012 Giulio Collura 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | 19 | using Gtk; 20 | 21 | namespace Launcher.Widgets { 22 | 23 | struct Page { 24 | public uint rows; 25 | public uint columns; 26 | public int number; 27 | } 28 | 29 | public class Grid : Gtk.Box { 30 | 31 | public Widgets.Switcher page_switcher; 32 | 33 | private Gtk.Stack stack; 34 | private Gtk.Grid current_grid; 35 | private Gee.HashMap grids; 36 | 37 | private uint current_row = 0; 38 | private uint current_col = 0; 39 | 40 | private Page page; 41 | 42 | public Grid (int rows, int columns) { 43 | page.rows = rows; 44 | page.columns = columns; 45 | page.number = 1; 46 | var main_grid = new Gtk.Grid (); 47 | main_grid.orientation = Gtk.Orientation.VERTICAL; 48 | main_grid.row_spacing = 6; 49 | main_grid.margin_bottom = 12; 50 | stack = new Gtk.Stack (); 51 | stack.expand = true; 52 | stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT; 53 | page_switcher = new Widgets.Switcher (); 54 | page_switcher.set_stack (stack); 55 | var fake_grid = new Gtk.Grid (); 56 | fake_grid.expand = true; 57 | 58 | main_grid.add (stack); 59 | main_grid.add (fake_grid); 60 | main_grid.add (page_switcher); 61 | add (main_grid); 62 | 63 | grids = new Gee.HashMap (null, null); 64 | create_new_grid (); 65 | go_to_number (1); 66 | } 67 | 68 | private void create_new_grid () { 69 | // Grid properties 70 | current_grid = new Gtk.Grid (); 71 | current_grid.expand = true; 72 | current_grid.row_homogeneous = true; 73 | current_grid.column_homogeneous = true; 74 | current_grid.margin_start = 25; 75 | current_grid.margin_end = 25; 76 | current_grid.margin_top = 25; 77 | current_grid.margin_bottom = 25; 78 | 79 | current_grid.row_spacing = Pixels.ROW_SPACING; 80 | current_grid.column_spacing = 0; 81 | grids.set (page.number, current_grid); 82 | stack.add_titled (current_grid, page.number.to_string (), page.number.to_string ()); 83 | 84 | // Fake grids in case there are not enough apps to fill the grid 85 | for (var row = 0; row < page.rows; row++) 86 | for (var column = 0; column < page.columns; column++) 87 | current_grid.attach (new Gtk.Grid (), column, row, 1, 1); 88 | } 89 | 90 | public void append (Gtk.Widget widget) { 91 | update_position (); 92 | 93 | current_grid.attach (widget, (int)current_col, (int)current_row, 1, 1); 94 | current_col++; 95 | current_grid.show (); 96 | } 97 | 98 | private void update_position () { 99 | if (current_col == page.columns) { 100 | current_col = 0; 101 | current_row++; 102 | } 103 | 104 | if (current_row == page.rows) { 105 | page.number++; 106 | create_new_grid (); 107 | current_row = 0; 108 | } 109 | } 110 | 111 | public void clear () { 112 | foreach (Gtk.Grid grid in grids.values) { 113 | foreach (Gtk.Widget widget in grid.get_children ()) { 114 | widget.destroy (); 115 | } 116 | 117 | grid.destroy (); 118 | } 119 | 120 | grids.clear (); 121 | current_row = 0; 122 | current_col = 0; 123 | page.number = 1; 124 | create_new_grid (); 125 | stack.set_visible_child (current_grid); 126 | } 127 | 128 | public Gtk.Widget? get_child_at (int column, int row) { 129 | var col = ((int)(column/page.columns))+1; 130 | 131 | var grid = grids.get (col); 132 | if (grid != null) { 133 | return grid.get_child_at (column - (int)page.columns*(col-1), row) as Widgets.AppEntry; 134 | } else { 135 | return null; 136 | } 137 | } 138 | 139 | public int get_page_columns () { 140 | return (int) page.columns; 141 | } 142 | 143 | public int get_page_rows () { 144 | return (int) page.rows; 145 | } 146 | 147 | public int get_n_pages () { 148 | return (int) page.number; 149 | } 150 | 151 | public int get_current_page () { 152 | return int.parse (stack.get_visible_child_name ()); 153 | } 154 | 155 | public void go_to_next () { 156 | int page_number = get_current_page ()+1; 157 | if (page_number <= get_n_pages ()) 158 | stack.set_visible_child_name (page_number.to_string ()); 159 | 160 | page_switcher.update_selected (); 161 | } 162 | 163 | public void go_to_previous () { 164 | int page_number = get_current_page ()-1; 165 | if (page_number > 0) 166 | stack.set_visible_child_name (page_number.to_string ()); 167 | 168 | page_switcher.update_selected (); 169 | } 170 | 171 | public void go_to_last () { 172 | stack.set_visible_child_name (get_n_pages ().to_string ()); 173 | page_switcher.update_selected (); 174 | } 175 | 176 | public void go_to_number (int number) { 177 | stack.set_visible_child_name (number.to_string ()); 178 | page_switcher.update_selected (); 179 | } 180 | 181 | public void resize (int rows, int columns) { 182 | clear (); 183 | page.rows = rows; 184 | page.columns = columns; 185 | page.number = 1; 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /lib/synapse-core/config-service.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Michal Hruby 3 | * 2017 elementary LLC. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public 16 | * License along with this program; if not, write to the 17 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 | * Boston, MA 02110-1301 USA 19 | * 20 | * Authored by: Michal Hruby 21 | */ 22 | 23 | using Json; 24 | 25 | namespace Synapse { 26 | public abstract class ConfigObject : GLib.Object { } 27 | 28 | public class ConfigService : GLib.Object { 29 | // singleton that can be easily destroyed 30 | private static unowned ConfigService? instance; 31 | 32 | public static ConfigService get_default () { 33 | return instance ?? new ConfigService (); 34 | } 35 | 36 | private ConfigService () { } 37 | 38 | ~ConfigService () { 39 | // useless cause the timer takes a reference on self 40 | if (save_timer_id != 0) save (); 41 | instance = null; 42 | } 43 | 44 | private Json.Node root_node; 45 | private string config_file_name; 46 | private uint save_timer_id = 0; 47 | 48 | construct { 49 | instance = this; 50 | 51 | var parser = new Parser (); 52 | config_file_name = 53 | GLib.Path.build_filename (Environment.get_user_config_dir (), "synapse", "config.json"); 54 | try { 55 | parser.load_from_file (config_file_name); 56 | root_node = parser.get_root ().copy (); 57 | if (root_node.get_node_type () != NodeType.OBJECT) { 58 | root_node = new Json.Node (NodeType.OBJECT); 59 | root_node.take_object (new Json.Object ()); 60 | } 61 | } catch (Error err) { 62 | root_node = new Json.Node (NodeType.OBJECT); 63 | root_node.take_object (new Json.Object ()); 64 | } 65 | } 66 | 67 | /** 68 | * Creates an instance of an object derived from ConfigObject class, which 69 | * will have its public properties set to values stored in config file, or 70 | * to the default values if this object wasn't yet stored. 71 | * 72 | * @param group A group name. 73 | * @param key A key name. 74 | * @param config_type Type of the object (must be subclass of ConfigObject) 75 | * @return An instance of config_type. 76 | */ 77 | public ConfigObject get_config (string group, string key, Type config_type) { 78 | unowned Json.Object obj = root_node.get_object (); 79 | unowned Json.Node group_node = obj.get_member (group); 80 | 81 | if (group_node != null) { 82 | if (group_node.get_node_type () == NodeType.OBJECT) { 83 | unowned Json.Object group_obj = group_node.get_object (); 84 | unowned Json.Node key_node = group_obj.get_member (key); 85 | 86 | if (key_node != null && key_node.get_node_type () == NodeType.OBJECT) { 87 | var result = Json.gobject_deserialize (config_type, key_node); 88 | return result as ConfigObject; 89 | } 90 | } 91 | } 92 | 93 | return GLib.Object.new (config_type) as ConfigObject; 94 | } 95 | 96 | /** 97 | * Behaves in a similar way to get_config, but it also watches for changes 98 | * in the returned config object and saves them back to the config file 99 | * (without the need of calling set_config). 100 | * 101 | * @param group A group name. 102 | * @param key A key name. 103 | * @param config_type Type of the object (must be subclass of ConfigObject) 104 | */ 105 | public ConfigObject bind_config (string group, string key, Type config_type) { 106 | ConfigObject config_object = get_config (group, key, config_type); 107 | // make sure the lambda doesn't take a ref on the config_object 108 | unowned ConfigObject co = config_object; 109 | co.notify.connect (() => { this.set_config (group, key, co); }); 110 | 111 | return config_object; 112 | } 113 | 114 | /** 115 | * Stores all public properties of the object to the config file under 116 | * specified group and key names. 117 | * 118 | * @param group A group name. 119 | * @param key A key name. 120 | * @param cfg_obj ConfigObject instance. 121 | */ 122 | public void set_config (string group, string key, ConfigObject cfg_obj) { 123 | unowned Json.Object obj = root_node.get_object (); 124 | if (!obj.has_member (group) || obj.get_member (group).get_node_type () != NodeType.OBJECT) { 125 | // why set_object_member works, but set_member doesn't ?! 126 | obj.set_object_member (group, new Json.Object ()); 127 | } 128 | 129 | unowned Json.Object group_obj = obj.get_object_member (group); 130 | // why the hell is this necessary? 131 | if (group_obj.has_member (key)) { 132 | group_obj.remove_member (key); 133 | } 134 | 135 | Json.Node node = Json.gobject_serialize (cfg_obj); 136 | group_obj.set_object_member (key, node.get_object ()); 137 | 138 | if (save_timer_id != 0) { 139 | Source.remove (save_timer_id); 140 | } 141 | // on crap, this takes a reference on self 142 | save_timer_id = Timeout.add (30000, this.save_timeout); 143 | } 144 | 145 | private bool save_timeout () { 146 | save_timer_id = 0; 147 | save (); 148 | 149 | return false; 150 | } 151 | 152 | /** 153 | * Forces immediate saving of the configuration file to the filesystem. 154 | */ 155 | public void save () { 156 | if (save_timer_id != 0) { 157 | Source.remove (save_timer_id); 158 | save_timer_id = 0; 159 | } 160 | 161 | var generator = new Generator (); 162 | generator.pretty = true; 163 | generator.set_root (root_node); 164 | 165 | DirUtils.create_with_parents (GLib.Path.get_dirname (config_file_name), 0755); 166 | try { 167 | generator.to_file (config_file_name); 168 | } catch (Error err) { 169 | warning ("%s", err.message); 170 | } 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /data/panther-applications.menu: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Applications 7 | X-GNOME-Menu-Applications.directory 8 | 9 | 10 | /etc/X11/applnk 11 | /usr/share/gnome/apps 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Accessories 23 | Utility.directory 24 | 25 | 26 | Utility 27 | 31 | Accessibility 32 | System 33 | org.gnome.font-viewer.desktop 34 | gnome-font-viewer.desktop 35 | file-roller.desktop 36 | org.gnome.FileRoller.desktop 37 | 38 | 39 | 40 | 41 | 42 | 43 | Universal Access 44 | Utility-Accessibility.directory 45 | 46 | 47 | Accessibility 48 | Settings 49 | 50 | orca.desktop 51 | onboard.desktop 52 | 53 | 54 | 55 | 56 | 57 | 58 | Development 59 | Development.directory 60 | 61 | 62 | Development 63 | 64 | emacs.desktop 65 | 66 | 67 | 68 | 69 | 70 | Education 71 | Education.directory 72 | 73 | 74 | Education 75 | Science 76 | 77 | 78 | 79 | 80 | 81 | 82 | Science 83 | GnomeScience.directory 84 | 85 | 86 | Education 87 | Science 88 | 89 | 90 | 91 | 92 | 93 | 94 | Games 95 | Game.directory 96 | 97 | 98 | Game 99 | 100 | steam.desktop 101 | 102 | 103 | 104 | 105 | 106 | Graphics 107 | Graphics.directory 108 | 109 | 110 | Graphics 111 | evince.desktop 112 | 113 | 114 | 115 | 116 | 117 | 118 | Internet 119 | Network.directory 120 | 121 | 122 | Network 123 | steam.desktop 124 | 125 | 126 | 127 | 128 | 129 | 130 | Multimedia 131 | AudioVideo.directory 132 | 133 | 134 | AudioVideo 135 | 136 | 137 | 138 | 139 | 140 | 141 | Office 142 | Office.directory 143 | 144 | 145 | Office 146 | evince.desktop 147 | 148 | 149 | 150 | 151 | 152 | 153 | System 154 | System-Tools.directory 155 | 156 | 157 | 158 | System 159 | Administration 160 | 161 | 162 | Game 163 | X-GNOME-Settings-Panel 164 | debian-xterm.desktop 165 | debian-uxterm.desktop 166 | htop.desktop 167 | ibus.desktop 168 | 169 | 170 | ubuntuone-installer.desktop 171 | ubuntu-tweak.desktop 172 | 173 | 174 | 175 | 176 | 177 | Settings 178 | Settings.directory 179 | 180 | 181 | 182 | 183 | Core 184 | Screensaver 185 | X-GNOME-Settings-Panel 186 | debian-xterm.desktop 187 | debian-uxterm.desktop 188 | htop.desktop 189 | ibus.desktop 190 | empathy-accounts.desktop 191 | orca.desktop 192 | onboard.desktop 193 | org.gnome.font-viewer.desktop 194 | gnome-font-viewer.desktop 195 | evince.desktop 196 | file-roller.desktop 197 | org.gnome.FileRoller.desktop 198 | 199 | 200 | Settings 201 | 202 | Settings 203 | 204 | DesktopSettings 205 | Security 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | Wine 215 | Wine.directory 216 | 217 | 218 | Wine 219 | 220 | 221 | 222 | 223 | 224 | 225 | Settings 226 | 227 | 228 | 229 | 230 | 231 | Debian 232 | debian-menu.menu 233 | Debian.directory 234 | 235 | 236 | 237 | 238 | switchboard 239 | Switchboard.directory 240 | 241 | 242 | 243 | X-PANTHEON-Switchboard-Plugs 244 | X-GNOME-Settings-Panel 245 | 246 | 247 | 248 | 249 | 250 | 251 | -------------------------------------------------------------------------------- /lib/synapse-core/query.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Michal Hruby 3 | * 2017 elementary LLC. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public 16 | * License along with this program; if not, write to the 17 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 | * Boston, MA 02110-1301 USA 19 | * 20 | * Authored by: Michal Hruby 21 | */ 22 | 23 | namespace Synapse { 24 | [Flags] 25 | public enum QueryFlags { 26 | /* HowTo create categories (32bit). 27 | * Authored by Alberto Aldegheri 28 | * Categories are "stored" in 3 Levels: 29 | * Super-Category 30 | * -> Category 31 | * ----> Sub-Category 32 | * ------------------------------------ 33 | * if (Super-Category does NOT have childs): 34 | * SUPER = 1 << FreeBitPosition 35 | * else: 36 | * if (Category does NOT have childs) 37 | * CATEGORY = 1 << FreeBitPosition 38 | * else 39 | * SUB = 1 << FreeBitPosition 40 | * CATEGORY = OR ([subcategories, ...]); 41 | * 42 | * SUPER = OR ([categories, ...]); 43 | * 44 | * 45 | * Remember: 46 | * if you add or remove a category, 47 | * change labels in UIInterface.CategoryConfig.init_labels 48 | * 49 | */ 50 | INCLUDE_REMOTE = 1 << 0, 51 | UNCATEGORIZED = 1 << 1, 52 | 53 | APPLICATIONS = 1 << 2, 54 | 55 | ACTIONS = 1 << 3, 56 | 57 | AUDIO = 1 << 4, 58 | VIDEO = 1 << 5, 59 | DOCUMENTS = 1 << 6, 60 | IMAGES = 1 << 7, 61 | FILES = AUDIO | VIDEO | DOCUMENTS | IMAGES, 62 | 63 | PLACES = 1 << 8, 64 | 65 | // FIXME: shouldn't this be FILES | INCLUDE_REMOTE? 66 | INTERNET = 1 << 9, 67 | 68 | // FIXME: Text Query flag? kinda weird, why do we have this here? 69 | TEXT = 1 << 10, 70 | 71 | CONTACTS = 1 << 11, 72 | 73 | ALL = 0xFFFFFFFF, 74 | LOCAL_CONTENT = ALL ^ QueryFlags.INCLUDE_REMOTE 75 | } 76 | 77 | [Flags] 78 | public enum MatcherFlags { 79 | NO_REVERSED = 1 << 0, 80 | NO_SUBSTRING = 1 << 1, 81 | NO_PARTIAL = 1 << 2, 82 | NO_FUZZY = 1 << 3 83 | } 84 | 85 | public struct Query { 86 | string query_string; 87 | string query_string_folded; 88 | Cancellable cancellable; 89 | QueryFlags query_type; 90 | uint max_results; 91 | uint query_id; 92 | 93 | public Query (uint query_id, string query, QueryFlags flags = QueryFlags.LOCAL_CONTENT, uint num_results = 96) { 94 | this.query_id = query_id; 95 | this.query_string = query; 96 | this.query_string_folded = query.casefold (); 97 | this.query_type = flags; 98 | this.max_results = num_results; 99 | } 100 | 101 | public bool is_cancelled () { 102 | return cancellable.is_cancelled (); 103 | } 104 | 105 | public void check_cancellable () throws SearchError { 106 | if (cancellable.is_cancelled ()) { 107 | throw new SearchError.SEARCH_CANCELLED ("Cancelled"); 108 | } 109 | } 110 | 111 | public static Gee.List> get_matchers_for_query (string query, MatcherFlags match_flags = 0, RegexCompileFlags flags = GLib.RegexCompileFlags.OPTIMIZE) { 112 | /* create a couple of regexes and try to help with matching 113 | * match with these regular expressions (with descending score): 114 | * 1) ^query$ 115 | * 2) ^query 116 | * 3) \bquery 117 | * 4) split to words and seach \bword1.+\bword2 (if there are 2+ words) 118 | * 5) query 119 | * 6) split to characters and search \bq.+\bu.+\be.+\br.+\by 120 | * 7) split to characters and search \bq.*u.*e.*r.*y 121 | * 122 | * The set of returned regular expressions depends on MatcherFlags. 123 | */ 124 | 125 | var results = new Gee.HashMap (); 126 | Regex re; 127 | 128 | try { 129 | re = new Regex ("^(%s)$".printf (Regex.escape_string (query)), flags); 130 | results[re] = Match.Score.HIGHEST; 131 | } catch (RegexError err) { } 132 | 133 | try { 134 | re = new Regex ("^(%s)".printf (Regex.escape_string (query)), flags); 135 | results[re] = Match.Score.EXCELLENT; 136 | } catch (RegexError err) { } 137 | 138 | try { 139 | re = new Regex ("\\b(%s)".printf (Regex.escape_string (query)), flags); 140 | results[re] = Match.Score.VERY_GOOD; 141 | } catch (RegexError err) { } 142 | 143 | // split to individual chars 144 | string[] individual_words = Regex.split_simple ("\\s+", query.strip ()); 145 | if (individual_words.length >= 2) { 146 | string[] escaped_words = {}; 147 | foreach (unowned string word in individual_words) { 148 | escaped_words += Regex.escape_string (word); 149 | } 150 | string pattern = "\\b(%s)".printf (string.joinv (").+\\b(", escaped_words)); 151 | 152 | try { 153 | re = new Regex (pattern, flags); 154 | results[re] = Match.Score.GOOD; 155 | } catch (RegexError err) { } 156 | 157 | // FIXME: do something generic here 158 | if (!(MatcherFlags.NO_REVERSED in match_flags)) { 159 | if (escaped_words.length == 2) { 160 | var reversed = "\\b(%s)".printf (string.join (").+\\b(", escaped_words[1], escaped_words[0], null)); 161 | try { 162 | re = new Regex (reversed, flags); 163 | results[re] = Match.Score.GOOD - Match.Score.INCREMENT_MINOR; 164 | } catch (RegexError err) { } 165 | } else { 166 | // not too nice, but is quite fast to compute 167 | var orred = "\\b((?:%s))".printf (string.joinv (")|(?:", escaped_words)); 168 | var any_order = ""; 169 | for (int i=0; i 0) { 209 | string pattern = "\\b(%s)".printf (string.joinv (").*(", escaped_chars)); 210 | 211 | try { 212 | re = new Regex (pattern, flags); 213 | results[re] = Match.Score.POOR; 214 | } catch (RegexError err) { } 215 | } 216 | 217 | var sorted_results = new Gee.ArrayList> (); 218 | var entries = results.entries; 219 | // FIXME: why it doesn't work without this? 220 | sorted_results.set_data ("entries-ref", entries); 221 | sorted_results.add_all (entries); 222 | sorted_results.sort ((a, b) => { 223 | unowned Gee.Map.Entry e1 = (Gee.Map.Entry) a; 224 | unowned Gee.Map.Entry e2 = (Gee.Map.Entry) b; 225 | return e2.value - e1.value; 226 | }); 227 | 228 | return sorted_results; 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/Backend/SynapseSearch.vala: -------------------------------------------------------------------------------- 1 | // -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- 2 | // 3 | // Copyright (C) 2011-2012 Giulio Collura 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | 19 | using Soup; 20 | 21 | namespace Launcher.Backend { 22 | 23 | public class SynapseSearch : Object { 24 | 25 | private static Type[] plugins = { 26 | typeof (Synapse.CalculatorPlugin), 27 | typeof (Synapse.CommandPlugin), 28 | typeof (Synapse.DesktopFilePlugin), 29 | typeof (Synapse.SystemManagementPlugin), 30 | typeof (Synapse.WebSearchPlugin), 31 | }; 32 | 33 | private static Synapse.DataSink? sink = null; 34 | private static Gee.HashMap favicon_cache; 35 | 36 | Cancellable? current_search = null; 37 | 38 | public SynapseSearch () { 39 | init_sink(); 40 | } 41 | 42 | private void init_sink () { 43 | if (sink == null) { 44 | sink = new Synapse.DataSink (); 45 | foreach (var plugin in plugins) { 46 | sink.register_static_plugin (plugin); 47 | } 48 | 49 | favicon_cache = new Gee.HashMap (); 50 | } 51 | } 52 | 53 | public void get_system_actions () { 54 | message("get_system_actions"); 55 | if (sink == null) 56 | init_sink(); 57 | 58 | //message(sink.is_plugin_enabled("Synapse.SystemManagementPlugin").to_string()); 59 | 60 | var smp = sink.get_plugin("Synapse.SystemManagementPlugin"); 61 | 62 | /* Gee.List actions = smp.get_actions(); 63 | 64 | actions.foreach((action) => { 65 | warning(action.title); 66 | 67 | return true; 68 | });*/ 69 | 70 | } 71 | 72 | public async Gee.List? search (string text, Synapse.SearchProvider? provider = null) { 73 | 74 | if (current_search != null) 75 | current_search.cancel (); 76 | 77 | if (provider == null) 78 | provider = sink; 79 | 80 | var results = new Synapse.ResultSet (); 81 | 82 | try { 83 | return yield provider.search (text, Synapse.QueryFlags.ALL, results, current_search); 84 | } catch (Error e) { warning (e.message); } 85 | 86 | return null; 87 | } 88 | 89 | public async Gee.List? search_actions (string text, Synapse.SearchProvider? provider = null) { 90 | 91 | if (current_search != null) 92 | current_search.cancel (); 93 | 94 | if (provider == null) 95 | provider = sink; 96 | 97 | var results = new Synapse.ResultSet (); 98 | 99 | try { 100 | return yield provider.search (text, Synapse.QueryFlags.ACTIONS, results, current_search); 101 | } catch (Error e) { warning (e.message); } 102 | 103 | return null; 104 | } 105 | 106 | public static Gee.List find_actions_for_match (Synapse.Match match) { 107 | return sink.find_actions_for_match (match, null, Synapse.QueryFlags.ALL); 108 | } 109 | 110 | /** 111 | * Attempts to load a favicon for an UriMatch and caches the icon 112 | * 113 | * @param match The UriMatch 114 | * @param size The icon size at which to load the icon. If the favicon is smaller than 115 | * that size, null will be returned 116 | * @param cancellable Cancellable for the loading operations 117 | * @return The pixbuf or null if loading failed or the icon was too small 118 | */ 119 | public static async Gdk.Pixbuf? get_favicon_for_match (Synapse.UriMatch match, int size, 120 | Cancellable? cancellable = null) { 121 | 122 | var soup_uri = new Soup.URI (match.uri); 123 | if (!(soup_uri.scheme.has_prefix ("http") || soup_uri.scheme.has_prefix ("https"))) 124 | return null; 125 | 126 | Gdk.Pixbuf? pixbuf = null; 127 | 128 | if (favicon_cache.has_key (soup_uri.host)) 129 | return favicon_cache.get (soup_uri.host); 130 | 131 | var url = "%s://%s/favicon.ico".printf (soup_uri.scheme, soup_uri.host); 132 | 133 | var msg = new Soup.Message ("GET", url); 134 | var session = new Soup.Session (); 135 | session.use_thread_context = true; 136 | 137 | try { 138 | var stream = yield session.send_async (msg, cancellable); 139 | if (stream != null) { 140 | pixbuf = yield new Gdk.Pixbuf.from_stream_async (stream, cancellable); 141 | // as per design decision, icons that are smaller than requested will not 142 | // be displayed, instead the fallback should be used, so we return null 143 | if (pixbuf.width < size) 144 | pixbuf = null; 145 | } 146 | } catch (Error e) {} 147 | 148 | if (cancellable.is_cancelled ()) 149 | return null; 150 | 151 | // we set the cache in any case, even if things failed. No need to 152 | // try requesting an icon again and again 153 | favicon_cache.set (soup_uri.host, pixbuf); 154 | 155 | return pixbuf; 156 | } 157 | 158 | // copied from synapse-ui with some slight changes 159 | public static string markup_string_with_search (string text, string pattern) { 160 | 161 | string markup = "%s"; 162 | 163 | if (pattern == "") { 164 | return markup.printf (Markup.escape_text (text)); 165 | } 166 | 167 | // if no text found, use pattern 168 | if (text == "") { 169 | return markup.printf (Markup.escape_text (pattern)); 170 | } 171 | 172 | var matchers = Synapse.Query.get_matchers_for_query (pattern, 0, 173 | RegexCompileFlags.OPTIMIZE | RegexCompileFlags.CASELESS); 174 | 175 | string? highlighted = null; 176 | foreach (var matcher in matchers) { 177 | MatchInfo mi; 178 | if (matcher.key.match (text, 0, out mi)) { 179 | int start_pos; 180 | int end_pos; 181 | int last_pos = 0; 182 | int cnt = mi.get_match_count (); 183 | StringBuilder res = new StringBuilder (); 184 | for (int i = 1; i < cnt; i++) { 185 | mi.fetch_pos (i, out start_pos, out end_pos); 186 | warn_if_fail (start_pos >= 0 && end_pos >= 0); 187 | res.append (Markup.escape_text (text.substring (last_pos, start_pos - last_pos))); 188 | last_pos = end_pos; 189 | res.append (Markup.printf_escaped ("%s", mi.fetch (i))); 190 | if (i == cnt - 1) { 191 | res.append (Markup.escape_text (text.substring (last_pos))); 192 | } 193 | } 194 | highlighted = res.str; 195 | break; 196 | } 197 | } 198 | 199 | if (highlighted != null) { 200 | return markup.printf (highlighted); 201 | } else { 202 | return markup.printf (Markup.escape_text(text)); 203 | } 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /lib/synapse-core/relevancy-backend-zg.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Michal Hruby 3 | * 2017 elementary LLC. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public 16 | * License along with this program; if not, write to the 17 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 | * Boston, MA 02110-1301 USA 19 | * 20 | * Authored by: Michal Hruby 21 | */ 22 | 23 | #if HAVE_ZEITGEIST 24 | using Zeitgeist; 25 | //using GLib.Math 26 | 27 | namespace Synapse { 28 | private class ZeitgeistRelevancyBackend: Object, RelevancyBackend { 29 | private Zeitgeist.Log zg_log; 30 | private Zeitgeist.DataSourceRegistry zg_dsr; 31 | private Gee.Map application_popularity; 32 | private Gee.Map uri_popularity; 33 | private bool has_datahub_gio_module = false; 34 | 35 | private const float MULTIPLIER = 65535.0f; 36 | 37 | construct { 38 | zg_log = new Zeitgeist.Log (); 39 | application_popularity = new Gee.HashMap (); 40 | uri_popularity = new Gee.HashMap (); 41 | 42 | refresh_popularity (); 43 | check_data_sources.begin (); 44 | 45 | Timeout.add_seconds (60*30, refresh_popularity); 46 | } 47 | 48 | private async void check_data_sources () { 49 | zg_dsr = new Zeitgeist.DataSourceRegistry (); 50 | try { 51 | var array = yield zg_dsr.get_data_sources (null); 52 | 53 | array.foreach ((ds) => { 54 | if (ds.unique_id == "com.zeitgeist-project,datahub,gio-launch-listener" && ds.enabled) { 55 | has_datahub_gio_module = true; 56 | return; 57 | } 58 | }); 59 | } catch (Error err) { 60 | warning ("Unable to check Zeitgeist data sources: %s", err.message); 61 | } 62 | } 63 | 64 | private bool refresh_popularity () { 65 | load_application_relevancies.begin (); 66 | load_uri_relevancies.begin (); 67 | 68 | return true; 69 | } 70 | 71 | private async void load_application_relevancies () { 72 | Idle.add (load_application_relevancies.callback, Priority.LOW); 73 | yield; 74 | 75 | int64 end = new DateTime.now_local ().to_unix () * 1000; 76 | int64 start = end - Zeitgeist.Timestamp.WEEK * 4; 77 | Zeitgeist.TimeRange tr = new Zeitgeist.TimeRange (start, end); 78 | 79 | var event = new Zeitgeist.Event (); 80 | event.interpretation = "!" + Zeitgeist.ZG.LEAVE_EVENT; 81 | var subject = new Zeitgeist.Subject (); 82 | subject.interpretation = Zeitgeist.NFO.SOFTWARE; 83 | subject.uri = "application://*"; 84 | event.add_subject (subject); 85 | 86 | var array = new GenericArray (); 87 | array.add (event); 88 | 89 | Zeitgeist.ResultSet rs; 90 | 91 | try { 92 | rs = yield zg_log.find_events (tr, array, 93 | Zeitgeist.StorageState.ANY, 94 | 256, 95 | Zeitgeist.ResultType.MOST_POPULAR_SUBJECTS, 96 | null); 97 | 98 | application_popularity.clear (); 99 | uint size = rs.size (); 100 | uint index = 0; 101 | 102 | // Zeitgeist (0.6) doesn't have any stats API, so let's approximate 103 | 104 | foreach (Zeitgeist.Event e in rs) { 105 | if (e.num_subjects () <= 0) { 106 | continue; 107 | } 108 | Zeitgeist.Subject s = e.subjects[0]; 109 | 110 | float power = index / (size * 2) + 0.5f; // linearly <0.5, 1.0> 111 | float relevancy = 1.0f / Math.powf (index + 1, power); 112 | application_popularity[s.uri] = (int)(relevancy * MULTIPLIER); 113 | 114 | index++; 115 | } 116 | } catch (Error err) { 117 | warning ("%s", err.message); 118 | return; 119 | } 120 | } 121 | 122 | private async void load_uri_relevancies () { 123 | Idle.add (load_uri_relevancies.callback, Priority.LOW); 124 | yield; 125 | 126 | int64 end = new DateTime.now_local ().to_unix () * 1000; 127 | int64 start = end - Zeitgeist.Timestamp.WEEK * 4; 128 | Zeitgeist.TimeRange tr = new Zeitgeist.TimeRange (start, end); 129 | 130 | var event = new Zeitgeist.Event (); 131 | event.interpretation = "!" + Zeitgeist.ZG.LEAVE_EVENT; 132 | var subject = new Zeitgeist.Subject (); 133 | subject.interpretation = "!" + Zeitgeist.NFO.SOFTWARE; 134 | subject.uri = "file://*"; 135 | event.add_subject (subject); 136 | 137 | var array = new GenericArray (); 138 | array.add (event); 139 | 140 | Zeitgeist.ResultSet rs; 141 | Gee.Map popularity_map = new Gee.HashMap (); 142 | 143 | try { 144 | uint size, index; 145 | float power, relevancy; 146 | /* Get popularity for file uris */ 147 | rs = yield zg_log.find_events (tr, array, 148 | Zeitgeist.StorageState.ANY, 149 | 256, 150 | Zeitgeist.ResultType.MOST_POPULAR_SUBJECTS, 151 | null); 152 | 153 | size = rs.size (); 154 | index = 0; 155 | 156 | // Zeitgeist (0.6) doesn't have any stats API, so let's approximate 157 | 158 | foreach (Zeitgeist.Event e1 in rs) { 159 | if (e1.num_subjects () <= 0) { 160 | continue; 161 | } 162 | Zeitgeist.Subject s1 = e1.subjects[0]; 163 | 164 | power = index / (size * 2) + 0.5f; // linearly <0.5, 1.0> 165 | relevancy = 1.0f / Math.powf (index + 1, power); 166 | popularity_map[s1.uri] = (int)(relevancy * MULTIPLIER); 167 | 168 | index++; 169 | } 170 | 171 | /* Get popularity for web uris */ 172 | subject.interpretation = Zeitgeist.NFO.WEBSITE; 173 | subject.uri = ""; 174 | array = new GenericArray (); 175 | array.add (event); 176 | 177 | rs = yield zg_log.find_events (tr, array, 178 | Zeitgeist.StorageState.ANY, 179 | 128, 180 | Zeitgeist.ResultType.MOST_POPULAR_SUBJECTS, 181 | null); 182 | 183 | size = rs.size (); 184 | index = 0; 185 | 186 | // Zeitgeist (0.6) doesn't have any stats API, so let's approximate 187 | 188 | foreach (Zeitgeist.Event e2 in rs) { 189 | if (e2.num_subjects () <= 0) { 190 | continue; 191 | } 192 | Zeitgeist.Subject s2 = e2.subjects[0]; 193 | 194 | power = index / (size * 2) + 0.5f; // linearly <0.5, 1.0> 195 | relevancy = 1.0f / Math.powf (index + 1, power); 196 | popularity_map[s2.uri] = (int)(relevancy * MULTIPLIER); 197 | 198 | index++; 199 | } 200 | } catch (Error err) { 201 | warning ("%s", err.message); 202 | } 203 | 204 | uri_popularity = popularity_map; 205 | } 206 | 207 | public float get_application_popularity (string desktop_id) { 208 | if (application_popularity.has_key (desktop_id)) { 209 | return application_popularity[desktop_id] / MULTIPLIER; 210 | } 211 | 212 | return 0.0f; 213 | } 214 | 215 | public float get_uri_popularity (string uri) { 216 | if (uri_popularity.has_key (uri)) { 217 | return uri_popularity[uri] / MULTIPLIER; 218 | } 219 | 220 | return 0.0f; 221 | } 222 | 223 | private void reload_relevancies () { 224 | Idle.add_full (Priority.LOW, () => 225 | { 226 | load_application_relevancies.begin (); 227 | return false; 228 | }); 229 | } 230 | 231 | public void application_launched (AppInfo app_info) { 232 | // FIXME: get rid of this maverick-specific workaround 233 | // detect if the Zeitgeist GIO module is installed 234 | Type zg_gio_module = Type.from_name ("GAppLaunchHandlerZeitgeist"); 235 | // FIXME: perhaps we should check app_info.should_show? 236 | // but user specifically asked to open this, so probably not 237 | // otoh the gio module won't pick it up if it's not should_show 238 | if (zg_gio_module != 0) { 239 | Utils.Logger.debug (this, "libzg-gio-module detected, not pushing"); 240 | reload_relevancies (); 241 | 242 | return; 243 | } 244 | 245 | if (has_datahub_gio_module) { 246 | reload_relevancies (); 247 | return; 248 | } 249 | 250 | string app_uri = null; 251 | if (app_info.get_id () != null) { 252 | app_uri = "application://" + app_info.get_id (); 253 | } else if (app_info is DesktopAppInfo) { 254 | string? filename = (app_info as DesktopAppInfo).get_filename (); 255 | if (filename == null) { 256 | return; 257 | } 258 | app_uri = "application://" + Path.get_basename (filename); 259 | } 260 | 261 | Utils.Logger.debug (this, "launched \"%s\", pushing to ZG", app_uri); 262 | push_app_launch (app_uri, app_info.get_display_name ()); 263 | 264 | // and refresh 265 | reload_relevancies (); 266 | } 267 | 268 | private void push_app_launch (string app_uri, string? display_name) { 269 | //debug ("pushing launch event: %s [%s]", app_uri, display_name); 270 | var event = new Zeitgeist.Event (); 271 | var subject = new Zeitgeist.Subject (); 272 | 273 | event.actor = "application://synapse.desktop"; 274 | event.interpretation = Zeitgeist.ZG.ACCESS_EVENT; 275 | event.manifestation = Zeitgeist.ZG.USER_ACTIVITY; 276 | event.add_subject (subject); 277 | 278 | subject.uri = app_uri; 279 | subject.interpretation = Zeitgeist.NFO.SOFTWARE; 280 | subject.manifestation = Zeitgeist.NFO.SOFTWARE_ITEM; 281 | subject.mimetype ="application/x-desktop"; 282 | subject.text = display_name; 283 | 284 | try { 285 | zg_log.insert_event_no_reply (event); 286 | } catch (Error err) { 287 | warning ("%s", err.message); 288 | return; 289 | } 290 | } 291 | } 292 | } 293 | #endif 294 | -------------------------------------------------------------------------------- /src/Widgets/AppEntry.vala: -------------------------------------------------------------------------------- 1 | // -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- 2 | // 3 | // Copyright (C) 2011-2012 Giulio Collura 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | 19 | using Gtk; 20 | 21 | public class Launcher.Widgets.AppEntry : Gtk.Button { 22 | private static Gtk.Menu menu; 23 | 24 | public Gtk.Label app_label; 25 | private Gdk.Pixbuf icon; 26 | private new Gtk.Image image; 27 | 28 | public string exec_name; 29 | public string app_name; 30 | public string desktop_id; 31 | public int icon_size; 32 | public string desktop_path; 33 | 34 | public File launchers_dir; 35 | 36 | public signal void app_launched (); 37 | 38 | private bool dragging = false; //prevent launching 39 | 40 | private Backend.App application; 41 | 42 | #if HAS_PLANK 43 | static construct { 44 | plank_client = Plank.DBusClient.get_instance (); 45 | } 46 | 47 | private static Plank.DBusClient plank_client; 48 | private bool docked = false; 49 | private string desktop_uri; 50 | #endif 51 | 52 | public AppEntry (Backend.App app) { 53 | Gtk.TargetEntry dnd = {"text/uri-list", 0, 0}; 54 | Gtk.drag_source_set (this, Gdk.ModifierType.BUTTON1_MASK, {dnd}, 55 | Gdk.DragAction.COPY); 56 | 57 | desktop_id = app.desktop_id; 58 | desktop_path = app.desktop_path; 59 | #if HAS_PLANK 60 | desktop_uri = File.new_for_path (desktop_path).get_uri (); 61 | #endif 62 | 63 | application = app; 64 | app_name = app.name; 65 | tooltip_text = app.description; 66 | exec_name = app.exec; 67 | icon_size = Launchy.settings.icon_size; 68 | icon = app.icon; 69 | 70 | get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); 71 | get_style_context ().add_class("app_button"); 72 | 73 | if (Launchy.settings.font_size <= 0.001) { 74 | app_label = new Gtk.Label (app_name); 75 | app_label.use_markup = false; 76 | } else { 77 | var texto = "%s".printf((int)(Launchy.settings.font_size * 1000),app_name); 78 | app_label = new Gtk.Label(null); 79 | app_label.set_markup(texto); 80 | } 81 | 82 | app_label.halign = Gtk.Align.CENTER; 83 | app_label.justify = Gtk.Justification.CENTER; 84 | app_label.set_line_wrap (true); 85 | app_label.lines = 2; 86 | app_label.set_single_line_mode (false); 87 | app_label.wrap_mode = Pango.WrapMode.WORD_CHAR; 88 | app_label.set_ellipsize (Pango.EllipsizeMode.END); 89 | //app_label.get_style_context ().add_class("app_label"); 90 | 91 | image = new Gtk.Image.from_pixbuf (icon); 92 | image.icon_size = icon_size; 93 | image.margin_top = 12; 94 | 95 | var grid = new Gtk.Grid (); 96 | grid.orientation = Gtk.Orientation.VERTICAL; 97 | grid.row_spacing = 6; 98 | grid.expand = true; 99 | grid.halign = Gtk.Align.CENTER; 100 | grid.add (image); 101 | grid.add (app_label); 102 | 103 | add (grid); 104 | set_size_request (Pixels.ITEM_SIZE, Pixels.ITEM_SIZE); 105 | 106 | this.clicked.connect (launch_app); 107 | 108 | this.button_press_event.connect ((e) => { 109 | if (e.button != Gdk.BUTTON_SECONDARY) 110 | return false; 111 | 112 | create_menu (); 113 | if (menu != null && menu.get_children () != null) { 114 | menu.popup (null, null, null, e.button, e.time); 115 | return true; 116 | } 117 | return false; 118 | }); 119 | 120 | this.drag_begin.connect ( (ctx) => { 121 | this.dragging = true; 122 | Gtk.drag_set_icon_pixbuf (ctx, icon, 0, 0); 123 | }); 124 | 125 | this.drag_end.connect ( () => { 126 | this.dragging = false; 127 | var panther_app = (Gtk.Application) GLib.Application.get_default (); 128 | ((LaunchyView)panther_app.active_window).grab_device (); 129 | }); 130 | 131 | this.drag_data_get.connect ( (ctx, sel, info, time) => { 132 | sel.set_uris ({File.new_for_path (desktop_path).get_uri ()}); 133 | }); 134 | 135 | app.icon_changed.connect (() => { 136 | icon = app.icon; 137 | image.set_from_pixbuf (icon); 138 | }); 139 | 140 | } 141 | 142 | public override void get_preferred_width (out int minimum_width, out int natural_width) { 143 | minimum_width = Pixels.ITEM_SIZE; 144 | natural_width = Pixels.ITEM_SIZE; 145 | } 146 | 147 | public override void get_preferred_height (out int minimum_height, out int natural_height) { 148 | minimum_height = Pixels.ITEM_SIZE; 149 | natural_height = Pixels.ITEM_SIZE; 150 | } 151 | 152 | public void launch_app () { 153 | application.launch (); 154 | app_launched (); 155 | } 156 | 157 | private void create_menu () { 158 | // Display the apps static quicklist items in a popover menu 159 | if (application.actions == null) { 160 | try { 161 | application.init_actions (); 162 | } catch (KeyFileError e) { 163 | critical ("%s: %s", desktop_path, e.message); 164 | } 165 | } 166 | 167 | menu = new Gtk.Menu (); 168 | 169 | // Showing a menu reverts the effect of the grab_device function. 170 | menu.hide.connect (() => { 171 | var panther_app = (Gtk.Application) GLib.Application.get_default (); 172 | ((LaunchyView)panther_app.active_window).grab_device (); 173 | }); 174 | foreach (var action in application.actions) { 175 | var menuitem = new Gtk.MenuItem.with_mnemonic (action); 176 | menu.add (menuitem); 177 | menuitem.activate.connect (() => { 178 | try { 179 | var values = application.actions_map.get (action).split (";;"); 180 | AppInfo.create_from_commandline (values[0], null, AppInfoCreateFlags.NONE).launch (null, null); 181 | app_launched (); 182 | } catch (Error e) { 183 | critical ("%s: %s", desktop_path, e.message); 184 | } 185 | }); 186 | } 187 | 188 | if (menu.get_children ().length () > 0) 189 | menu.add (new Gtk.SeparatorMenuItem ()); 190 | 191 | menu.add(get_saved_menuitem ()); 192 | 193 | #if HAS_PLANK 194 | if (plank_client != null && plank_client.is_connected) { 195 | if (menu.get_children ().length () > 0) 196 | menu.add (new Gtk.SeparatorMenuItem ()); 197 | 198 | menu.add (get_plank_menuitem ()); 199 | } 200 | else { 201 | message("Not connected: " + plank_client.is_connected.to_string ()); 202 | } 203 | #endif 204 | 205 | menu.show_all (); 206 | } 207 | 208 | #if HAS_PLANK 209 | private Gtk.MenuItem get_plank_menuitem () { 210 | docked = (desktop_uri in plank_client.get_persistent_applications ()); 211 | 212 | var plank_menuitem = new Gtk.MenuItem (); 213 | plank_menuitem.set_use_underline (true); 214 | 215 | if (docked) 216 | plank_menuitem.set_label (_("Remove from _Dock")); 217 | else 218 | plank_menuitem.set_label (_("Pin to _Dock")); 219 | 220 | plank_menuitem.activate.connect (plank_menuitem_activate); 221 | 222 | return plank_menuitem; 223 | } 224 | 225 | private void plank_menuitem_activate () { 226 | if (plank_client == null || !plank_client.is_connected) 227 | return; 228 | 229 | if (docked) 230 | plank_client.remove_item (desktop_uri); 231 | else 232 | plank_client.add_item (desktop_uri); 233 | } 234 | #endif 235 | private Gtk.MenuItem get_saved_menuitem () { 236 | //docked = (desktop_uri in plank_client.get_persistent_applications ()); 237 | var panther_app = (Gtk.Application) GLib.Application.get_default (); 238 | bool saved = ((LaunchyView)panther_app.active_window).cat_saved; 239 | 240 | var saved_menuitem = new Gtk.MenuItem (); 241 | saved_menuitem.set_use_underline (true); 242 | 243 | if (saved) 244 | saved_menuitem.set_label (_("Remove from Starred")); 245 | else 246 | saved_menuitem.set_label (_("Add to Starred")); 247 | 248 | saved_menuitem.activate.connect (saved_menuitem_activate); 249 | 250 | return saved_menuitem; 251 | } 252 | 253 | private void saved_menuitem_activate () { 254 | var panther_app = (Gtk.Application) GLib.Application.get_default (); 255 | bool saved = ((LaunchyView)panther_app.active_window).cat_saved; 256 | 257 | if(saved) 258 | remove_saved_item (); 259 | else 260 | add_saved_item (); 261 | } 262 | 263 | private void add_saved_item () { 264 | string uri = desktop_uri; 265 | File? target_dir = null; 266 | 267 | if (target_dir == null) 268 | { 269 | target_dir = File.new_for_path (Environment.get_user_config_dir () + "/panther/saved/"); 270 | 271 | if (!target_dir.query_exists ()) 272 | try { 273 | target_dir.make_directory_with_parents (); 274 | } catch (Error e) { 275 | critical ("Could not access or create the directory '%s'. (%s)", target_dir.get_path () ?? "", e.message); 276 | } 277 | } 278 | 279 | bool is_valid = false; 280 | string basename; 281 | var launcher_file = File.new_for_uri (uri); 282 | is_valid = launcher_file.query_exists (); 283 | basename = (launcher_file.get_basename () ?? "unknown"); 284 | 285 | if (is_valid) { 286 | var file = new KeyFile (); 287 | 288 | try { 289 | // find a unique file name, based on the name of the launcher 290 | var index_of_last_dot = basename.last_index_of ("."); 291 | var launcher_base = (index_of_last_dot >= 0 ? basename.slice (0, index_of_last_dot) : basename); 292 | var dockitem = "%s.saveditem".printf (launcher_base); 293 | var dockitem_file = target_dir.get_child (dockitem); 294 | 295 | if (!dockitem_file.query_exists ()) { 296 | // save the key file 297 | var stream = new DataOutputStream (dockitem_file.create (FileCreateFlags.NONE)); 298 | stream.put_string (file.to_data ()); 299 | stream.close (); 300 | 301 | var panther_app = (Gtk.Application) GLib.Application.get_default (); 302 | ((LaunchyView)panther_app.active_window).amend_stared (basename); 303 | } 304 | } catch (Error e){ 305 | warning(e.message); 306 | } 307 | } 308 | } 309 | 310 | private void remove_saved_item () { 311 | string uri = desktop_uri; 312 | File? target_dir = null; 313 | File saved_file = null; 314 | 315 | if (target_dir == null) 316 | target_dir = File.new_for_path (Environment.get_home_dir () + "/.config/panther/saved/"); 317 | 318 | bool is_valid = false; 319 | string basename; 320 | var launcher_file = File.new_for_uri (uri); 321 | //is_valid = launcher_file.query_exists (); 322 | basename = (launcher_file.get_basename () ?? "unknown"); 323 | 324 | var saveditem = "%s%s.saveditem".printf (target_dir.get_path () + "/", basename.substring(0, basename.length - 8)); 325 | message("removing saved file: " + saveditem); 326 | saved_file = File.new_for_path(saveditem); 327 | is_valid = saved_file.query_exists (); 328 | 329 | if(is_valid){ 330 | try{ 331 | saved_file.delete (); 332 | 333 | var panther_app = (Gtk.Application) GLib.Application.get_default (); 334 | ((LaunchyView)panther_app.active_window).amend_stared (basename); 335 | 336 | } catch (Error e){ 337 | warning(e.message); 338 | } 339 | } 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /lib/synapse-core/utils.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Michal Hruby 3 | * 2017 elementary LLC. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public 16 | * License along with this program; if not, write to the 17 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 | * Boston, MA 02110-1301 USA 19 | * 20 | * Authored by: Michal Hruby 21 | */ 22 | 23 | namespace Synapse { 24 | [CCode (gir_namespace = "SynapseUtils", gir_version = "1.0")] 25 | namespace Utils { 26 | /* Make sure setlocale was called before calling this function 27 | * (Gtk.init calls it automatically) 28 | */ 29 | public static string? remove_accents (string input) { 30 | string? result; 31 | unowned string charset; 32 | GLib.get_charset (out charset); 33 | 34 | try { 35 | result = GLib.convert (input, input.length, 36 | "US-ASCII//TRANSLIT", charset); 37 | // no need to waste cpu cycles if the input is the same 38 | if (input == result) { 39 | return null; 40 | } 41 | } catch (ConvertError err) { 42 | result = null; 43 | } 44 | 45 | return result; 46 | } 47 | 48 | public static string? remove_last_unichar (string input) { 49 | long char_count = input.char_count (); 50 | int len = input.index_of_nth_char (char_count - 1); 51 | 52 | return input.substring (0, len); 53 | } 54 | 55 | public static async bool query_exists_async (GLib.File f) { 56 | bool exists; 57 | 58 | try { 59 | yield f.query_info_async (FileAttribute.STANDARD_TYPE, 0, 0, null); 60 | exists = true; 61 | } catch (Error err) { 62 | exists = false; 63 | } 64 | 65 | return exists; 66 | } 67 | 68 | public string extract_type_name (Type obj_type) { 69 | string obj_class = obj_type.name (); 70 | if (obj_class.has_prefix ("Synapse")) { 71 | return obj_class.substring (7); 72 | } 73 | 74 | return obj_class; 75 | } 76 | 77 | public class Logger { 78 | protected const string RED = "\x1b[31m"; 79 | protected const string GREEN = "\x1b[32m"; 80 | protected const string YELLOW = "\x1b[33m"; 81 | protected const string BLUE = "\x1b[34m"; 82 | protected const string MAGENTA = "\x1b[35m"; 83 | protected const string CYAN = "\x1b[36m"; 84 | protected const string RESET = "\x1b[0m"; 85 | 86 | private static bool initialized = false; 87 | private static bool show_debug = false; 88 | 89 | private static void log_internal (Object? obj, LogLevelFlags level, string format, va_list args) { 90 | if (!initialized) { 91 | initialize (); 92 | } 93 | string desc = ""; 94 | if (obj != null) { 95 | string obj_class = extract_type_name (obj.get_type ()); 96 | desc = "%s[%s]%s ".printf (MAGENTA, obj_class, RESET); 97 | } 98 | logv ("Synapse", level, desc + format, args); 99 | } 100 | 101 | private static void initialize () { 102 | var levels = LogLevelFlags.LEVEL_DEBUG | LogLevelFlags.LEVEL_INFO | LogLevelFlags.LEVEL_WARNING | LogLevelFlags.LEVEL_CRITICAL | LogLevelFlags.LEVEL_ERROR; 103 | string[] domains = { 104 | "Synapse", 105 | "Gtk", 106 | "Gdk", 107 | "GLib", 108 | "GLib-GObject", 109 | "Pango", 110 | "GdkPixbuf", 111 | "GLib-GIO", 112 | "GtkHotkey" 113 | }; 114 | 115 | foreach (unowned string domain in domains) { 116 | Log.set_handler (domain, levels, handler); 117 | } 118 | Log.set_handler (null, levels, handler); 119 | 120 | show_debug = Environment.get_variable ("SYNAPSE_DEBUG") != null; 121 | initialized = true; 122 | } 123 | 124 | public static bool debug_enabled () { 125 | if (!initialized) initialize (); 126 | return show_debug; 127 | } 128 | 129 | public static void log (Object? obj, string format, ...) { 130 | var args = va_list (); 131 | log_internal (obj, LogLevelFlags.LEVEL_INFO, format, args); 132 | } 133 | 134 | [Diagnostics] 135 | public static void debug (Object? obj, string format, ...) { 136 | var args = va_list (); 137 | log_internal (obj, LogLevelFlags.LEVEL_DEBUG, format, args); 138 | } 139 | 140 | public static void warning (Object? obj, string format, ...) { 141 | var args = va_list (); 142 | log_internal (obj, LogLevelFlags.LEVEL_WARNING, format, args); 143 | } 144 | 145 | public static void error (Object? obj, string format, ...) { 146 | var args = va_list (); 147 | log_internal (obj, LogLevelFlags.LEVEL_ERROR, format, args); 148 | } 149 | 150 | protected static void handler (string? domain, LogLevelFlags level, string msg) { 151 | string header; 152 | string domain_str = ""; 153 | if (domain != null && domain != "Synapse") { 154 | domain_str = domain + "-"; 155 | } 156 | 157 | var time_val = TimeVal (); 158 | long time_str_len = time_val.tv_usec != 0 ? 15 : 8; 159 | string cur_time = time_val.to_iso8601 ().substring (11, time_str_len); 160 | 161 | if (level == LogLevelFlags.LEVEL_DEBUG) { 162 | if (!show_debug && domain_str == "") { 163 | return; 164 | } 165 | header = @"$(GREEN)[$(cur_time) $(domain_str)Debug]$(RESET)"; 166 | } else if (level == LogLevelFlags.LEVEL_INFO) { 167 | header = @"$(BLUE)[$(cur_time) $(domain_str)Info]$(RESET)"; 168 | } else if (level == LogLevelFlags.LEVEL_WARNING) { 169 | header = @"$(RED)[$(cur_time) $(domain_str)Warning]$(RESET)"; 170 | } else if (level == LogLevelFlags.LEVEL_CRITICAL || level == LogLevelFlags.LEVEL_ERROR) { 171 | header = @"$(RED)[$(cur_time) $(domain_str)Critical]$(RESET)"; 172 | } else { 173 | header = @"$(YELLOW)[$(cur_time)]$(RESET)"; 174 | } 175 | 176 | stdout.printf ("%s %s\n", header, msg); 177 | #if 0 178 | void* buffer[10]; 179 | int num = Linux.backtrace (&buffer, 10); 180 | string[] symbols = Linux.backtrace_symbols (buffer, num); 181 | if (symbols != null) 182 | { 183 | for (int i = 0; i < num; i++) stdout.printf ("%s\n", symbols[i]); 184 | } 185 | #endif 186 | } 187 | } 188 | 189 | [Compact] 190 | private class DelegateWrapper { 191 | public SourceFunc callback; 192 | 193 | public DelegateWrapper (owned SourceFunc cb) { 194 | callback = (owned) cb; 195 | } 196 | } 197 | 198 | /* 199 | * Asynchronous Once. 200 | * 201 | * Usage: 202 | * private AsyncOnce once = new AsyncOnce (); 203 | * public async void foo () 204 | * { 205 | * if (!once.is_initialized ()) // not stricly necessary but improves perf 206 | * { 207 | * if (yield once.enter ()) 208 | * { 209 | * // this block will be executed only once, but the method 210 | * // is reentrant; it's also recommended to wrap this block 211 | * // in try { } and call once.leave() in finally { } 212 | * // if any of the operations can throw an error 213 | * var s = yield get_the_string (); 214 | * once.leave (s); 215 | * } 216 | * } 217 | * // if control reaches this point the once was initialized 218 | * yield do_something_for_string (once.get_data ()); 219 | * } 220 | */ 221 | public class AsyncOnce { 222 | private enum OperationState { 223 | NOT_STARTED, 224 | IN_PROGRESS, 225 | DONE 226 | } 227 | 228 | private G inner; 229 | 230 | private OperationState state; 231 | private DelegateWrapper[] callbacks = {}; 232 | 233 | public AsyncOnce () { 234 | state = OperationState.NOT_STARTED; 235 | } 236 | 237 | public unowned G get_data () { 238 | return inner; 239 | } 240 | 241 | public bool is_initialized () { 242 | return state == OperationState.DONE; 243 | } 244 | 245 | public async bool enter () { 246 | if (state == OperationState.NOT_STARTED) { 247 | state = OperationState.IN_PROGRESS; 248 | return true; 249 | } else if (state == OperationState.IN_PROGRESS) { 250 | yield wait_async (); 251 | } 252 | 253 | return false; 254 | } 255 | 256 | public void leave (G result) { 257 | if (state != OperationState.IN_PROGRESS) { 258 | warning ("Incorrect usage of AsyncOnce"); 259 | return; 260 | } 261 | 262 | state = OperationState.DONE; 263 | inner = result; 264 | notify_all (); 265 | } 266 | 267 | /* Once probably shouldn't have this, but it's useful */ 268 | public void reset () { 269 | if (state == OperationState.IN_PROGRESS) { 270 | warning ("AsyncOnce.reset() cannot be called in the middle of initialization."); 271 | } else { 272 | state = OperationState.NOT_STARTED; 273 | inner = null; 274 | } 275 | } 276 | 277 | private void notify_all () { 278 | foreach (unowned DelegateWrapper wrapper in callbacks) { 279 | wrapper.callback (); 280 | } 281 | callbacks = {}; 282 | } 283 | 284 | private async void wait_async () { 285 | callbacks += new DelegateWrapper (wait_async.callback); 286 | yield; 287 | } 288 | } 289 | 290 | public class FileInfo { 291 | private static string interesting_attributes; 292 | static construct { 293 | interesting_attributes = 294 | string.join (",", FileAttribute.STANDARD_TYPE, 295 | FileAttribute.STANDARD_IS_HIDDEN, 296 | FileAttribute.STANDARD_IS_BACKUP, 297 | FileAttribute.STANDARD_DISPLAY_NAME, 298 | FileAttribute.STANDARD_ICON, 299 | FileAttribute.STANDARD_FAST_CONTENT_TYPE, 300 | FileAttribute.THUMBNAIL_PATH, 301 | null); 302 | } 303 | 304 | public string uri; 305 | public string parse_name; 306 | public QueryFlags file_type; 307 | public UriMatch? match_obj; 308 | private bool initialized; 309 | private Type match_obj_type; 310 | 311 | public FileInfo (string uri, Type obj_type) { 312 | assert (obj_type.is_a (typeof (UriMatch))); 313 | this.uri = uri; 314 | this.match_obj = null; 315 | this.match_obj_type = obj_type; 316 | this.initialized = false; 317 | this.file_type = QueryFlags.UNCATEGORIZED; 318 | 319 | var f = File.new_for_uri (uri); 320 | this.parse_name = f.get_parse_name (); 321 | } 322 | 323 | public bool is_initialized () { 324 | return this.initialized; 325 | } 326 | 327 | public async void initialize () { 328 | initialized = true; 329 | var f = File.new_for_uri (uri); 330 | 331 | try { 332 | var fi = yield f.query_info_async (interesting_attributes, 0, 0, null); 333 | if (fi.get_file_type () == FileType.REGULAR && !fi.get_is_hidden () && !fi.get_is_backup ()) { 334 | match_obj = (UriMatch) Object.new (match_obj_type, 335 | "thumbnail-path", fi.get_attribute_byte_string (FileAttribute.THUMBNAIL_PATH), 336 | "icon-name", fi.get_icon ().to_string (), 337 | "uri", uri, 338 | "title", fi.get_display_name (), 339 | "description", f.get_parse_name (), 340 | "match-type", MatchType.GENERIC_URI, 341 | null); 342 | 343 | // let's determine the file type 344 | unowned string mime_type = fi.get_attribute_string (FileAttribute.STANDARD_FAST_CONTENT_TYPE); 345 | if (ContentType.is_unknown (mime_type)) { 346 | file_type = QueryFlags.UNCATEGORIZED; 347 | } else if (ContentType.is_a (mime_type, "audio/*")) { 348 | file_type = QueryFlags.AUDIO; 349 | } else if (ContentType.is_a (mime_type, "video/*")) { 350 | file_type = QueryFlags.VIDEO; 351 | } else if (ContentType.is_a (mime_type, "image/*")) { 352 | file_type = QueryFlags.IMAGES; 353 | } else if (ContentType.is_a (mime_type, "text/*")) { 354 | file_type = QueryFlags.DOCUMENTS; 355 | } else if (ContentType.is_a (mime_type, "application/*")) { // FIXME: this isn't right 356 | file_type = QueryFlags.DOCUMENTS; 357 | } 358 | 359 | match_obj.file_type = file_type; 360 | match_obj.mime_type = mime_type; 361 | } 362 | } catch (Error err) { 363 | warning ("%s", err.message); 364 | } 365 | } 366 | 367 | public async bool exists () { 368 | var f = File.new_for_uri (uri); 369 | bool result = yield query_exists_async (f); 370 | 371 | return result; 372 | } 373 | } 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /lib/synapse-core/common-actions.vala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Michal Hruby 3 | * 2017 elementary LLC. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public 16 | * License along with this program; if not, write to the 17 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 | * Boston, MA 02110-1301 USA 19 | * 20 | * Authored by: Michal Hruby 21 | */ 22 | 23 | namespace Synapse { 24 | public abstract class BaseAction: Object, Match { 25 | // from Match interface 26 | public string title { get; construct set; } 27 | public string description { get; set; } 28 | public string icon_name { get; construct set; } 29 | public bool has_thumbnail { get; construct set; } 30 | public string thumbnail_path { get; construct set; } 31 | public MatchType match_type { get; construct set; } 32 | 33 | public int default_relevancy { get; set; } 34 | public bool notify_match { get; set; default = true; } 35 | 36 | public abstract bool valid_for_match (Match match); 37 | public virtual int get_relevancy_for_match (Match match) { 38 | return default_relevancy; 39 | } 40 | 41 | public abstract void do_execute (Match? source, Match? target = null); 42 | public void execute_with_target (Match? source, Match? target = null) { 43 | do_execute (source, target); 44 | if (notify_match) source.executed (); 45 | } 46 | 47 | public virtual bool needs_target () { 48 | return false; 49 | } 50 | 51 | public virtual QueryFlags target_flags () { 52 | return QueryFlags.ALL; 53 | } 54 | } 55 | 56 | public class CommonActions: Object, Activatable, ActionProvider { 57 | public bool enabled { get; set; default = true; } 58 | 59 | public void activate () { } 60 | 61 | public void deactivate () { } 62 | 63 | private class Runner: BaseAction { 64 | public Runner () { 65 | Object (title: _("Run"), 66 | description: _("Run an application, action or script"), 67 | icon_name: "system-run", has_thumbnail: false, 68 | match_type: MatchType.ACTION, 69 | default_relevancy: Match.Score.EXCELLENT); 70 | } 71 | 72 | public override void do_execute (Match? match, Match? target = null) { 73 | if (match.match_type == MatchType.APPLICATION) { 74 | ApplicationMatch? app_match = match as ApplicationMatch; 75 | return_if_fail (app_match != null); 76 | 77 | AppInfo app = app_match.app_info ?? 78 | new DesktopAppInfo.from_filename (app_match.filename); 79 | 80 | try { 81 | var display = Gdk.Display.get_default (); 82 | app.launch (null, display.get_app_launch_context ()); 83 | 84 | RelevancyService.get_default ().application_launched (app); 85 | } catch (Error err) { 86 | Utils.Logger.warning (this, "%s", err.message); 87 | } 88 | } else { // MatchType.ACTION 89 | match.execute (null); 90 | } 91 | } 92 | 93 | public override bool valid_for_match (Match match) { 94 | switch (match.match_type) { 95 | case MatchType.SEARCH: 96 | return true; 97 | case MatchType.ACTION: 98 | return true; 99 | case MatchType.APPLICATION: 100 | ApplicationMatch? am = match as ApplicationMatch; 101 | return am == null || !am.needs_terminal; 102 | default: 103 | return false; 104 | } 105 | } 106 | } 107 | 108 | private class TerminalRunner: BaseAction { 109 | public TerminalRunner () { 110 | Object (title: _("Run in Terminal"), 111 | description: _("Run application or command in terminal"), 112 | icon_name: "terminal", has_thumbnail: false, 113 | match_type: MatchType.ACTION, 114 | default_relevancy: Match.Score.BELOW_AVERAGE); 115 | } 116 | 117 | public override void do_execute (Match? match, Match? target = null) { 118 | if (match.match_type == MatchType.APPLICATION) { 119 | ApplicationMatch? app_match = match as ApplicationMatch; 120 | return_if_fail (app_match != null); 121 | 122 | AppInfo original = app_match.app_info ?? 123 | new DesktopAppInfo.from_filename (app_match.filename); 124 | 125 | try { 126 | AppInfo app = AppInfo.create_from_commandline ( 127 | original.get_commandline (), original.get_name (), 128 | AppInfoCreateFlags.NEEDS_TERMINAL); 129 | var display = Gdk.Display.get_default (); 130 | app.launch (null, display.get_app_launch_context ()); 131 | } catch (Error err) { 132 | Utils.Logger.warning (this, "%s", err.message); 133 | } 134 | } 135 | } 136 | 137 | public override bool valid_for_match (Match match) { 138 | switch (match.match_type) { 139 | case MatchType.APPLICATION: 140 | ApplicationMatch? am = match as ApplicationMatch; 141 | return am != null; 142 | default: 143 | return false; 144 | } 145 | } 146 | } 147 | 148 | private class Opener: BaseAction { 149 | public Opener () { 150 | Object (title: _("Open"), 151 | description: _("Open using default application"), 152 | icon_name: "fileopen", has_thumbnail: false, 153 | match_type: MatchType.ACTION, 154 | default_relevancy: Match.Score.GOOD); 155 | } 156 | 157 | public override void do_execute (Match? match, Match? target = null) { 158 | UriMatch uri_match = match as UriMatch; 159 | 160 | if (uri_match != null) { 161 | CommonActions.open_uri (uri_match.uri); 162 | } else if (file_path.match (match.title)) { 163 | File f; 164 | if (match.title.has_prefix ("~")) { 165 | f = File.new_for_path (Path.build_filename (Environment.get_home_dir (), match.title.substring (1), null)); 166 | } else { 167 | f = File.new_for_path (match.title); 168 | } 169 | CommonActions.open_uri (f.get_uri ()); 170 | } else { 171 | CommonActions.open_uri (match.title); 172 | } 173 | } 174 | 175 | public override bool valid_for_match (Match match) { 176 | switch (match.match_type) { 177 | case MatchType.GENERIC_URI: 178 | return true; 179 | case MatchType.UNKNOWN: 180 | return web_uri.match (match.title) || file_path.match (match.title); 181 | default: 182 | return false; 183 | } 184 | } 185 | 186 | private Regex web_uri; 187 | private Regex file_path; 188 | 189 | construct { 190 | try { 191 | web_uri = new Regex ("^(ftp|http(s)?)://[^.]+\\.[^.]+", RegexCompileFlags.OPTIMIZE); 192 | file_path = new Regex ("^(/|~/)[^/]+", RegexCompileFlags.OPTIMIZE); 193 | } catch (Error err) { 194 | Utils.Logger.warning (this, "%s", err.message); 195 | } 196 | } 197 | } 198 | 199 | private class OpenFolder: BaseAction { 200 | public OpenFolder () { 201 | Object (title: _("Open folder"), 202 | description: _("Open folder containing this file"), 203 | icon_name: "folder-open", has_thumbnail: false, 204 | match_type: MatchType.ACTION, 205 | default_relevancy: Match.Score.AVERAGE); 206 | } 207 | 208 | public override void do_execute (Match? match, Match? target = null) { 209 | UriMatch uri_match = match as UriMatch; 210 | return_if_fail (uri_match != null); 211 | var f = File.new_for_uri (uri_match.uri); 212 | f = f.get_parent (); 213 | try { 214 | var app_info = f.query_default_handler (null); 215 | List files = new List (); 216 | files.prepend (f); 217 | var display = Gdk.Display.get_default (); 218 | app_info.launch (files, display.get_app_launch_context ()); 219 | } catch (Error err) { 220 | Utils.Logger.warning (this, "%s", err.message); 221 | } 222 | } 223 | 224 | public override bool valid_for_match (Match match) { 225 | if (match.match_type != MatchType.GENERIC_URI) { 226 | return false; 227 | } 228 | UriMatch uri_match = match as UriMatch; 229 | var f = File.new_for_uri (uri_match.uri); 230 | var parent = f.get_parent (); 231 | 232 | return parent != null && f.is_native (); 233 | } 234 | } 235 | 236 | private class ClipboardCopy: BaseAction { 237 | public ClipboardCopy () { 238 | Object (title: _("Copy to Clipboard"), 239 | description: _("Copy selection to clipboard"), 240 | icon_name: "gtk-copy", has_thumbnail: false, 241 | match_type: MatchType.ACTION, 242 | default_relevancy: Match.Score.AVERAGE); 243 | } 244 | 245 | public override void do_execute (Match? match, Match? target = null) { 246 | var cb = Gtk.Clipboard.get (Gdk.Atom.NONE); 247 | if (match.match_type == MatchType.GENERIC_URI) { 248 | UriMatch uri_match = match as UriMatch; 249 | return_if_fail (uri_match != null); 250 | 251 | /* Just wow, Gtk and also Vala are trying really hard to make this hard to do... 252 | Gtk.TargetEntry[] no_entries = {}; 253 | Gtk.TargetList l = new Gtk.TargetList (no_entries); 254 | l.add_uri_targets (0); 255 | l.add_text_targets (0); 256 | Gtk.TargetEntry te = Gtk.target_table_new_from_list (l, 2); 257 | cb.set_with_data (); 258 | */ 259 | cb.set_text (uri_match.uri, -1); 260 | } else if (match.match_type == MatchType.TEXT) { 261 | TextMatch? text_match = match as TextMatch; 262 | string content = text_match != null ? text_match.get_text () : match.title; 263 | 264 | cb.set_text (content, -1); 265 | } 266 | } 267 | 268 | public override bool valid_for_match (Match match) { 269 | switch (match.match_type) { 270 | case MatchType.GENERIC_URI: 271 | return true; 272 | case MatchType.TEXT: 273 | return true; 274 | default: 275 | return false; 276 | } 277 | } 278 | 279 | public override int get_relevancy_for_match (Match match) { 280 | TextMatch? text_match = match as TextMatch; 281 | if (text_match != null && text_match.text_origin == TextOrigin.CLIPBOARD) { 282 | return 0; 283 | } 284 | 285 | return default_relevancy; 286 | } 287 | } 288 | 289 | private Gee.List actions; 290 | 291 | construct { 292 | actions = new Gee.ArrayList (); 293 | 294 | actions.add (new Runner ()); 295 | actions.add (new TerminalRunner ()); 296 | actions.add (new Opener ()); 297 | actions.add (new OpenFolder ()); 298 | actions.add (new ClipboardCopy ()); 299 | } 300 | 301 | public ResultSet? find_for_match (ref Query query, Match match) { 302 | bool query_empty = query.query_string == ""; 303 | var results = new ResultSet (); 304 | 305 | if (query_empty) { 306 | foreach (var action in actions) { 307 | if (action.valid_for_match (match)) { 308 | results.add (action, action.get_relevancy_for_match (match)); 309 | } 310 | } 311 | } else { 312 | var matchers = Query.get_matchers_for_query (query.query_string, 0, RegexCompileFlags.OPTIMIZE | RegexCompileFlags.CASELESS); 313 | foreach (var action in actions) { 314 | if (!action.valid_for_match (match)) { 315 | continue; 316 | } 317 | 318 | foreach (var matcher in matchers) { 319 | if (matcher.key.match (action.title)) { 320 | results.add (action, matcher.value); 321 | break; 322 | } 323 | } 324 | } 325 | } 326 | 327 | return results; 328 | } 329 | 330 | public static void open_uri (string uri) { 331 | var f = File.new_for_uri (uri); 332 | try { 333 | var app_info = f.query_default_handler (null); 334 | List files = new List (); 335 | files.prepend (f); 336 | var display = Gdk.Display.get_default (); 337 | app_info.launch (files, display.get_app_launch_context ()); 338 | } catch (Error err) { 339 | Utils.Logger.warning (null, "%s", err.message); 340 | } 341 | } 342 | } 343 | } 344 | --------------------------------------------------------------------------------