From 1a2e2e05dc673fea4e53af360bbac6e36410b04f Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 3 Dec 2015 12:56:15 +0200 Subject: [PATCH] initial commit: moving from sourceforge to github --- .config/argparse.py | 2362 ++++++++++++++++++++++++++ .config/help | 54 + .config/options.py | 100 ++ .config/repl.py | 18 + .config/rules.mk | 326 ++++ .config/tar.py | 178 ++ CHANGELOG | 455 +++++ COPYING | 21 + CREDITS | 21 + INSTALL | 17 + Makefile | 12 + NOTES | 21 + README | 64 + configure | 502 ++++++ data/Makefile | 9 + data/config/Makefile | 13 + data/config/default.in | 398 +++++ data/config/pager.in | 28 + data/images/Makefile | 36 + data/images/battery_0.png | Bin 0 -> 4150 bytes data/images/battery_1.png | Bin 0 -> 4266 bytes data/images/battery_2.png | Bin 0 -> 4243 bytes data/images/battery_3.png | Bin 0 -> 4191 bytes data/images/battery_4.png | Bin 0 -> 4124 bytes data/images/battery_5.png | Bin 0 -> 4029 bytes data/images/battery_6.png | Bin 0 -> 3914 bytes data/images/battery_7.png | Bin 0 -> 3866 bytes data/images/battery_8.png | Bin 0 -> 3684 bytes data/images/battery_charging_0.png | Bin 0 -> 4549 bytes data/images/battery_charging_1.png | Bin 0 -> 4592 bytes data/images/battery_charging_2.png | Bin 0 -> 4543 bytes data/images/battery_charging_3.png | Bin 0 -> 4513 bytes data/images/battery_charging_4.png | Bin 0 -> 4456 bytes data/images/battery_charging_5.png | Bin 0 -> 4438 bytes data/images/battery_charging_6.png | Bin 0 -> 4345 bytes data/images/battery_charging_7.png | Bin 0 -> 4243 bytes data/images/battery_charging_8.png | Bin 0 -> 4074 bytes data/images/battery_na.png | Bin 0 -> 4443 bytes data/images/dclock_glyphs.png | Bin 0 -> 4622 bytes data/images/default.xpm | 35 + data/images/gnome-session-halt.png | Bin 0 -> 1327 bytes data/images/gnome-session-reboot.png | Bin 0 -> 1503 bytes data/images/logo.png | Bin 0 -> 14133 bytes data/man/Makefile | 13 + data/man/fbpanel.1.in | 89 + dbg.h | 30 + exec/Makefile | 13 + exec/make_profile.in | 73 + exec/xlogout | 49 + fbpanel-2009.ebuild | 27 + fbpanel.ebuild | 25 + panel/Makefile | 21 + panel/bg.c | 325 ++++ panel/bg.h | 67 + panel/ev.c | 302 ++++ panel/ev.h | 79 + panel/gconf.c | 236 +++ panel/gconf.h | 27 + panel/gconf_panel.c | 415 +++++ panel/gconf_plugins.c | 123 ++ panel/gtkbar.c | 254 +++ panel/gtkbar.h | 76 + panel/gtkbgbox.c | 404 +++++ panel/gtkbgbox.h | 72 + panel/misc.c | 1045 ++++++++++++ panel/misc.h | 53 + panel/panel.c | 952 +++++++++++ panel/panel.h | 193 +++ panel/plugin.c | 233 +++ panel/plugin.h | 67 + panel/run.c | 24 + panel/run.h | 9 + panel/xconf.c | 389 +++++ panel/xconf.h | 49 + plugins/Makefile | 29 + plugins/battery/Makefile | 10 + plugins/battery/battery.c | 130 ++ plugins/battery/os_linux.c | 134 ++ plugins/chart/Makefile | 10 + plugins/chart/chart.c | 283 +++ plugins/chart/chart.h | 29 + plugins/cpu/Makefile | 10 + plugins/cpu/cpu.c | 173 ++ plugins/dclock/Makefile | 10 + plugins/dclock/dclock.c | 350 ++++ plugins/deskno/Makefile | 10 + plugins/deskno/deskno.c | 123 ++ plugins/deskno2/#Makefile# | 6 + plugins/deskno2/Makefile | 10 + plugins/deskno2/deskno2.c | 134 ++ plugins/genmon/Makefile | 10 + plugins/genmon/genmon.c | 120 ++ plugins/icons/Makefile | 10 + plugins/icons/icons.c | 539 ++++++ plugins/image/Makefile | 10 + plugins/image/image.c | 100 ++ plugins/launchbar/Makefile | 10 + plugins/launchbar/launchbar.c | 298 ++++ plugins/mem/Makefile | 10 + plugins/mem/mem.c | 221 +++ plugins/mem/mt.h | 11 + plugins/mem2/Makefile | 10 + plugins/mem2/mem2.c | 177 ++ plugins/menu/Makefile | 11 + plugins/menu/menu.c | 390 +++++ plugins/menu/system_menu.c | 338 ++++ plugins/meter/Makefile | 10 + plugins/meter/meter.c | 113 ++ plugins/meter/meter.h | 25 + plugins/net/Makefile | 10 + plugins/net/net.c | 233 +++ plugins/pager/Makefile | 10 + plugins/pager/pager.c | 850 +++++++++ plugins/separator/Makefile | 10 + plugins/separator/separator.c | 43 + plugins/space/Makefile | 10 + plugins/space/space.c | 59 + plugins/systray/Makefile | 13 + plugins/systray/egg-marshal.c | 2 + plugins/systray/eggmarshalers.c | 164 ++ plugins/systray/eggmarshalers.h | 36 + plugins/systray/eggtraymanager.c | 655 +++++++ plugins/systray/eggtraymanager.h | 88 + plugins/systray/fixedtip.c | 158 ++ plugins/systray/fixedtip.h | 40 + plugins/systray/libsystray.so | Bin 0 -> 137118 bytes plugins/systray/main.c | 181 ++ plugins/taskbar/Makefile | 10 + plugins/taskbar/taskbar.c | 1553 +++++++++++++++++ plugins/tclock/Makefile | 10 + plugins/tclock/tclock.c | 191 +++ plugins/unstable/Makefile | 7 + plugins/unstable/test/Makefile | 10 + plugins/unstable/test/test.c | 103 ++ plugins/volume/Makefile | 10 + plugins/volume/volume.c | 305 ++++ plugins/wincmd/Makefile | 10 + plugins/wincmd/wincmd.c | 220 +++ po/Makefile | 5 + po/fr_FR.UTF-8.mo | Bin 0 -> 2513 bytes po/fr_FR.UTF-8.po | 216 +++ po/ru_RU.UTF-8.mo | Bin 0 -> 3063 bytes po/ru_RU.UTF-8.po | 218 +++ scripts/Makefile | 5 + scripts/create_po.sh | 63 + scripts/custom.sh | 30 + scripts/endianess.sh | 18 + scripts/funcs.sh | 40 + scripts/install.sh | 21 + scripts/install_locale.sh | 24 + scripts/install_locale.sh.in | 24 + scripts/mk_tar | 53 + scripts/rfs-pkg-config | 36 + scripts/update-proj.sh | 17 + scripts/update_po.sh | 59 + version | 1 + 156 files changed, 20089 insertions(+) create mode 100644 .config/argparse.py create mode 100755 .config/help create mode 100644 .config/options.py create mode 100755 .config/repl.py create mode 100644 .config/rules.mk create mode 100755 .config/tar.py create mode 100644 CHANGELOG create mode 100644 COPYING create mode 100644 CREDITS create mode 100644 INSTALL create mode 100644 Makefile create mode 100644 NOTES create mode 100644 README create mode 100755 configure create mode 100644 data/Makefile create mode 100644 data/config/Makefile create mode 100644 data/config/default.in create mode 100644 data/config/pager.in create mode 100644 data/images/Makefile create mode 100644 data/images/battery_0.png create mode 100644 data/images/battery_1.png create mode 100644 data/images/battery_2.png create mode 100644 data/images/battery_3.png create mode 100644 data/images/battery_4.png create mode 100644 data/images/battery_5.png create mode 100644 data/images/battery_6.png create mode 100644 data/images/battery_7.png create mode 100644 data/images/battery_8.png create mode 100644 data/images/battery_charging_0.png create mode 100644 data/images/battery_charging_1.png create mode 100644 data/images/battery_charging_2.png create mode 100644 data/images/battery_charging_3.png create mode 100644 data/images/battery_charging_4.png create mode 100644 data/images/battery_charging_5.png create mode 100644 data/images/battery_charging_6.png create mode 100644 data/images/battery_charging_7.png create mode 100644 data/images/battery_charging_8.png create mode 100644 data/images/battery_na.png create mode 100644 data/images/dclock_glyphs.png create mode 100644 data/images/default.xpm create mode 100644 data/images/gnome-session-halt.png create mode 100644 data/images/gnome-session-reboot.png create mode 100644 data/images/logo.png create mode 100644 data/man/Makefile create mode 100644 data/man/fbpanel.1.in create mode 100644 dbg.h create mode 100644 exec/Makefile create mode 100755 exec/make_profile.in create mode 100755 exec/xlogout create mode 100644 fbpanel-2009.ebuild create mode 100644 fbpanel.ebuild create mode 100644 panel/Makefile create mode 100644 panel/bg.c create mode 100644 panel/bg.h create mode 100644 panel/ev.c create mode 100644 panel/ev.h create mode 100644 panel/gconf.c create mode 100644 panel/gconf.h create mode 100644 panel/gconf_panel.c create mode 100644 panel/gconf_plugins.c create mode 100644 panel/gtkbar.c create mode 100644 panel/gtkbar.h create mode 100644 panel/gtkbgbox.c create mode 100644 panel/gtkbgbox.h create mode 100644 panel/misc.c create mode 100644 panel/misc.h create mode 100644 panel/panel.c create mode 100644 panel/panel.h create mode 100644 panel/plugin.c create mode 100644 panel/plugin.h create mode 100644 panel/run.c create mode 100644 panel/run.h create mode 100644 panel/xconf.c create mode 100644 panel/xconf.h create mode 100644 plugins/Makefile create mode 100644 plugins/battery/Makefile create mode 100644 plugins/battery/battery.c create mode 100644 plugins/battery/os_linux.c create mode 100644 plugins/chart/Makefile create mode 100644 plugins/chart/chart.c create mode 100644 plugins/chart/chart.h create mode 100644 plugins/cpu/Makefile create mode 100644 plugins/cpu/cpu.c create mode 100644 plugins/dclock/Makefile create mode 100644 plugins/dclock/dclock.c create mode 100644 plugins/deskno/Makefile create mode 100644 plugins/deskno/deskno.c create mode 100644 plugins/deskno2/#Makefile# create mode 100644 plugins/deskno2/Makefile create mode 100644 plugins/deskno2/deskno2.c create mode 100644 plugins/genmon/Makefile create mode 100644 plugins/genmon/genmon.c create mode 100644 plugins/icons/Makefile create mode 100644 plugins/icons/icons.c create mode 100644 plugins/image/Makefile create mode 100644 plugins/image/image.c create mode 100644 plugins/launchbar/Makefile create mode 100644 plugins/launchbar/launchbar.c create mode 100644 plugins/mem/Makefile create mode 100644 plugins/mem/mem.c create mode 100644 plugins/mem/mt.h create mode 100644 plugins/mem2/Makefile create mode 100644 plugins/mem2/mem2.c create mode 100644 plugins/menu/Makefile create mode 100644 plugins/menu/menu.c create mode 100644 plugins/menu/system_menu.c create mode 100644 plugins/meter/Makefile create mode 100644 plugins/meter/meter.c create mode 100644 plugins/meter/meter.h create mode 100644 plugins/net/Makefile create mode 100644 plugins/net/net.c create mode 100644 plugins/pager/Makefile create mode 100644 plugins/pager/pager.c create mode 100644 plugins/separator/Makefile create mode 100644 plugins/separator/separator.c create mode 100644 plugins/space/Makefile create mode 100644 plugins/space/space.c create mode 100644 plugins/systray/Makefile create mode 100644 plugins/systray/egg-marshal.c create mode 100644 plugins/systray/eggmarshalers.c create mode 100644 plugins/systray/eggmarshalers.h create mode 100644 plugins/systray/eggtraymanager.c create mode 100644 plugins/systray/eggtraymanager.h create mode 100644 plugins/systray/fixedtip.c create mode 100644 plugins/systray/fixedtip.h create mode 100755 plugins/systray/libsystray.so create mode 100644 plugins/systray/main.c create mode 100644 plugins/taskbar/Makefile create mode 100644 plugins/taskbar/taskbar.c create mode 100644 plugins/tclock/Makefile create mode 100644 plugins/tclock/tclock.c create mode 100644 plugins/unstable/Makefile create mode 100644 plugins/unstable/test/Makefile create mode 100644 plugins/unstable/test/test.c create mode 100644 plugins/volume/Makefile create mode 100644 plugins/volume/volume.c create mode 100644 plugins/wincmd/Makefile create mode 100644 plugins/wincmd/wincmd.c create mode 100644 po/Makefile create mode 100644 po/fr_FR.UTF-8.mo create mode 100644 po/fr_FR.UTF-8.po create mode 100644 po/ru_RU.UTF-8.mo create mode 100644 po/ru_RU.UTF-8.po create mode 100644 scripts/Makefile create mode 100755 scripts/create_po.sh create mode 100644 scripts/custom.sh create mode 100755 scripts/endianess.sh create mode 100644 scripts/funcs.sh create mode 100755 scripts/install.sh create mode 100755 scripts/install_locale.sh create mode 100755 scripts/install_locale.sh.in create mode 100755 scripts/mk_tar create mode 100755 scripts/rfs-pkg-config create mode 100755 scripts/update-proj.sh create mode 100755 scripts/update_po.sh create mode 100644 version diff --git a/.config/argparse.py b/.config/argparse.py new file mode 100644 index 0000000..32d948c --- /dev/null +++ b/.config/argparse.py @@ -0,0 +1,2362 @@ +# Author: Steven J. Bethard . + +"""Command-line parsing library + +This module is an optparse-inspired command-line parsing library that: + + - handles both optional and positional arguments + - produces highly informative usage messages + - supports parsers that dispatch to sub-parsers + +The following is a simple usage example that sums integers from the +command-line and writes the result to a file:: + + parser = argparse.ArgumentParser( + description='sum the integers at the command line') + parser.add_argument( + 'integers', metavar='int', nargs='+', type=int, + help='an integer to be summed') + parser.add_argument( + '--log', default=sys.stdout, type=argparse.FileType('w'), + help='the file where the sum should be written') + args = parser.parse_args() + args.log.write('%s' % sum(args.integers)) + args.log.close() + +The module contains the following public classes: + + - ArgumentParser -- The main entry point for command-line parsing. As the + example above shows, the add_argument() method is used to populate + the parser with actions for optional and positional arguments. Then + the parse_args() method is invoked to convert the args at the + command-line into an object with attributes. + + - ArgumentError -- The exception raised by ArgumentParser objects when + there are errors with the parser's actions. Errors raised while + parsing the command-line are caught by ArgumentParser and emitted + as command-line messages. + + - FileType -- A factory for defining types of files to be created. As the + example above shows, instances of FileType are typically passed as + the type= argument of add_argument() calls. + + - Action -- The base class for parser actions. Typically actions are + selected by passing strings like 'store_true' or 'append_const' to + the action= argument of add_argument(). However, for greater + customization of ArgumentParser actions, subclasses of Action may + be defined and passed as the action= argument. + + - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, + ArgumentDefaultsHelpFormatter -- Formatter classes which + may be passed as the formatter_class= argument to the + ArgumentParser constructor. HelpFormatter is the default, + RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser + not to change the formatting for help text, and + ArgumentDefaultsHelpFormatter adds information about argument defaults + to the help. + +All other classes in this module are considered implementation details. +(Also note that HelpFormatter and RawDescriptionHelpFormatter are only +considered public as object names -- the API of the formatter objects is +still considered an implementation detail.) +""" + +__version__ = '1.2.1' +__all__ = [ + 'ArgumentParser', + 'ArgumentError', + 'ArgumentTypeError', + 'FileType', + 'HelpFormatter', + 'ArgumentDefaultsHelpFormatter', + 'RawDescriptionHelpFormatter', + 'RawTextHelpFormatter', + 'Namespace', + 'Action', + 'ONE_OR_MORE', + 'OPTIONAL', + 'PARSER', + 'REMAINDER', + 'SUPPRESS', + 'ZERO_OR_MORE', +] + + +import copy as _copy +import os as _os +import re as _re +import sys as _sys +import textwrap as _textwrap + +from gettext import gettext as _ + +try: + set +except NameError: + # for python < 2.4 compatibility (sets module is there since 2.3): + from sets import Set as set + +try: + basestring +except NameError: + basestring = str + +try: + sorted +except NameError: + # for python < 2.4 compatibility: + def sorted(iterable, reverse=False): + result = list(iterable) + result.sort() + if reverse: + result.reverse() + return result + + +def _callable(obj): + return hasattr(obj, '__call__') or hasattr(obj, '__bases__') + + +SUPPRESS = '==SUPPRESS==' + +OPTIONAL = '?' +ZERO_OR_MORE = '*' +ONE_OR_MORE = '+' +PARSER = 'A...' +REMAINDER = '...' +_UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args' + +# ============================= +# Utility functions and classes +# ============================= + +class _AttributeHolder(object): + """Abstract base class that provides __repr__. + + The __repr__ method returns a string in the format:: + ClassName(attr=name, attr=name, ...) + The attributes are determined either by a class-level attribute, + '_kwarg_names', or by inspecting the instance __dict__. + """ + + def __repr__(self): + type_name = type(self).__name__ + arg_strings = [] + for arg in self._get_args(): + arg_strings.append(repr(arg)) + for name, value in self._get_kwargs(): + arg_strings.append('%s=%r' % (name, value)) + return '%s(%s)' % (type_name, ', '.join(arg_strings)) + + def _get_kwargs(self): + return sorted(self.__dict__.items()) + + def _get_args(self): + return [] + + +def _ensure_value(namespace, name, value): + if getattr(namespace, name, None) is None: + setattr(namespace, name, value) + return getattr(namespace, name) + + +# =============== +# Formatting Help +# =============== + +class HelpFormatter(object): + """Formatter for generating usage messages and argument help strings. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def __init__(self, + prog, + indent_increment=2, + max_help_position=24, + width=None): + + # default setting for width + if width is None: + try: + width = int(_os.environ['COLUMNS']) + except (KeyError, ValueError): + width = 80 + width -= 2 + + self._prog = prog + self._indent_increment = indent_increment + self._max_help_position = max_help_position + self._width = width + + self._current_indent = 0 + self._level = 0 + self._action_max_length = 0 + + self._root_section = self._Section(self, None) + self._current_section = self._root_section + + self._whitespace_matcher = _re.compile(r'\s+') + self._long_break_matcher = _re.compile(r'\n\n\n+') + + # =============================== + # Section and indentation methods + # =============================== + def _indent(self): + self._current_indent += self._indent_increment + self._level += 1 + + def _dedent(self): + self._current_indent -= self._indent_increment + assert self._current_indent >= 0, 'Indent decreased below 0.' + self._level -= 1 + + class _Section(object): + + def __init__(self, formatter, parent, heading=None): + self.formatter = formatter + self.parent = parent + self.heading = heading + self.items = [] + + def format_help(self): + # format the indented section + if self.parent is not None: + self.formatter._indent() + join = self.formatter._join_parts + for func, args in self.items: + func(*args) + item_help = join([func(*args) for func, args in self.items]) + if self.parent is not None: + self.formatter._dedent() + + # return nothing if the section was empty + if not item_help: + return '' + + # add the heading if the section was non-empty + if self.heading is not SUPPRESS and self.heading is not None: + current_indent = self.formatter._current_indent + heading = '%*s%s:\n' % (current_indent, '', self.heading) + else: + heading = '' + + # join the section-initial newline, the heading and the help + return join(['\n', heading, item_help, '\n']) + + def _add_item(self, func, args): + self._current_section.items.append((func, args)) + + # ======================== + # Message building methods + # ======================== + def start_section(self, heading): + self._indent() + section = self._Section(self, self._current_section, heading) + self._add_item(section.format_help, []) + self._current_section = section + + def end_section(self): + self._current_section = self._current_section.parent + self._dedent() + + def add_text(self, text): + if text is not SUPPRESS and text is not None: + self._add_item(self._format_text, [text]) + + def add_usage(self, usage, actions, groups, prefix=None): + if usage is not SUPPRESS: + args = usage, actions, groups, prefix + self._add_item(self._format_usage, args) + + def add_argument(self, action): + if action.help is not SUPPRESS: + + # find all invocations + get_invocation = self._format_action_invocation + invocations = [get_invocation(action)] + for subaction in self._iter_indented_subactions(action): + invocations.append(get_invocation(subaction)) + + # update the maximum item length + invocation_length = max([len(s) for s in invocations]) + action_length = invocation_length + self._current_indent + self._action_max_length = max(self._action_max_length, + action_length) + + # add the item to the list + self._add_item(self._format_action, [action]) + + def add_arguments(self, actions): + for action in actions: + self.add_argument(action) + + # ======================= + # Help-formatting methods + # ======================= + def format_help(self): + help = self._root_section.format_help() + if help: + help = self._long_break_matcher.sub('\n\n', help) + help = help.strip('\n') + '\n' + return help + + def _join_parts(self, part_strings): + return ''.join([part + for part in part_strings + if part and part is not SUPPRESS]) + + def _format_usage(self, usage, actions, groups, prefix): + if prefix is None: + prefix = _('usage: ') + + # if usage is specified, use that + if usage is not None: + usage = usage % dict(prog=self._prog) + + # if no optionals or positionals are available, usage is just prog + elif usage is None and not actions: + usage = '%(prog)s' % dict(prog=self._prog) + + # if optionals and positionals are available, calculate usage + elif usage is None: + prog = '%(prog)s' % dict(prog=self._prog) + + # split optionals from positionals + optionals = [] + positionals = [] + for action in actions: + if action.option_strings: + optionals.append(action) + else: + positionals.append(action) + + # build full usage string + format = self._format_actions_usage + action_usage = format(optionals + positionals, groups) + usage = ' '.join([s for s in [prog, action_usage] if s]) + + # wrap the usage parts if it's too long + text_width = self._width - self._current_indent + if len(prefix) + len(usage) > text_width: + + # break usage into wrappable parts + part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' + opt_usage = format(optionals, groups) + pos_usage = format(positionals, groups) + opt_parts = _re.findall(part_regexp, opt_usage) + pos_parts = _re.findall(part_regexp, pos_usage) + assert ' '.join(opt_parts) == opt_usage + assert ' '.join(pos_parts) == pos_usage + + # helper for wrapping lines + def get_lines(parts, indent, prefix=None): + lines = [] + line = [] + if prefix is not None: + line_len = len(prefix) - 1 + else: + line_len = len(indent) - 1 + for part in parts: + if line_len + 1 + len(part) > text_width: + lines.append(indent + ' '.join(line)) + line = [] + line_len = len(indent) - 1 + line.append(part) + line_len += len(part) + 1 + if line: + lines.append(indent + ' '.join(line)) + if prefix is not None: + lines[0] = lines[0][len(indent):] + return lines + + # if prog is short, follow it with optionals or positionals + if len(prefix) + len(prog) <= 0.75 * text_width: + indent = ' ' * (len(prefix) + len(prog) + 1) + if opt_parts: + lines = get_lines([prog] + opt_parts, indent, prefix) + lines.extend(get_lines(pos_parts, indent)) + elif pos_parts: + lines = get_lines([prog] + pos_parts, indent, prefix) + else: + lines = [prog] + + # if prog is long, put it on its own line + else: + indent = ' ' * len(prefix) + parts = opt_parts + pos_parts + lines = get_lines(parts, indent) + if len(lines) > 1: + lines = [] + lines.extend(get_lines(opt_parts, indent)) + lines.extend(get_lines(pos_parts, indent)) + lines = [prog] + lines + + # join lines into usage + usage = '\n'.join(lines) + + # prefix with 'usage:' + return '%s%s\n\n' % (prefix, usage) + + def _format_actions_usage(self, actions, groups): + # find group indices and identify actions in groups + group_actions = set() + inserts = {} + for group in groups: + try: + start = actions.index(group._group_actions[0]) + except ValueError: + continue + else: + end = start + len(group._group_actions) + if actions[start:end] == group._group_actions: + for action in group._group_actions: + group_actions.add(action) + if not group.required: + if start in inserts: + inserts[start] += ' [' + else: + inserts[start] = '[' + inserts[end] = ']' + else: + if start in inserts: + inserts[start] += ' (' + else: + inserts[start] = '(' + inserts[end] = ')' + for i in range(start + 1, end): + inserts[i] = '|' + + # collect all actions format strings + parts = [] + for i, action in enumerate(actions): + + # suppressed arguments are marked with None + # remove | separators for suppressed arguments + if action.help is SUPPRESS: + parts.append(None) + if inserts.get(i) == '|': + inserts.pop(i) + elif inserts.get(i + 1) == '|': + inserts.pop(i + 1) + + # produce all arg strings + elif not action.option_strings: + part = self._format_args(action, action.dest) + + # if it's in a group, strip the outer [] + if action in group_actions: + if part[0] == '[' and part[-1] == ']': + part = part[1:-1] + + # add the action string to the list + parts.append(part) + + # produce the first way to invoke the option in brackets + else: + option_string = action.option_strings[0] + + # if the Optional doesn't take a value, format is: + # -s or --long + if action.nargs == 0: + part = '%s' % option_string + + # if the Optional takes a value, format is: + # -s ARGS or --long ARGS + else: + default = action.dest.upper() + args_string = self._format_args(action, default) + part = '%s %s' % (option_string, args_string) + + # make it look optional if it's not required or in a group + if not action.required and action not in group_actions: + part = '[%s]' % part + + # add the action string to the list + parts.append(part) + + # insert things at the necessary indices + for i in sorted(inserts, reverse=True): + parts[i:i] = [inserts[i]] + + # join all the action items with spaces + text = ' '.join([item for item in parts if item is not None]) + + # clean up separators for mutually exclusive groups + open = r'[\[(]' + close = r'[\])]' + text = _re.sub(r'(%s) ' % open, r'\1', text) + text = _re.sub(r' (%s)' % close, r'\1', text) + text = _re.sub(r'%s *%s' % (open, close), r'', text) + text = _re.sub(r'\(([^|]*)\)', r'\1', text) + text = text.strip() + + # return the text + return text + + def _format_text(self, text): + if '%(prog)' in text: + text = text % dict(prog=self._prog) + text_width = self._width - self._current_indent + indent = ' ' * self._current_indent + return self._fill_text(text, text_width, indent) + '\n\n' + + def _format_action(self, action): + # determine the required width and the entry label + help_position = min(self._action_max_length + 2, + self._max_help_position) + help_width = self._width - help_position + action_width = help_position - self._current_indent - 2 + action_header = self._format_action_invocation(action) + + # ho nelp; start on same line and add a final newline + if not action.help: + tup = self._current_indent, '', action_header + action_header = '%*s%s\n' % tup + + # short action name; start on the same line and pad two spaces + elif len(action_header) <= action_width: + tup = self._current_indent, '', action_width, action_header + action_header = '%*s%-*s ' % tup + indent_first = 0 + + # long action name; start on the next line + else: + tup = self._current_indent, '', action_header + action_header = '%*s%s\n' % tup + indent_first = help_position + + # collect the pieces of the action help + parts = [action_header] + + # if there was help for the action, add lines of help text + if action.help: + help_text = self._expand_help(action) + help_lines = self._split_lines(help_text, help_width) + parts.append('%*s%s\n' % (indent_first, '', help_lines[0])) + for line in help_lines[1:]: + parts.append('%*s%s\n' % (help_position, '', line)) + + # or add a newline if the description doesn't end with one + elif not action_header.endswith('\n'): + parts.append('\n') + + # if there are any sub-actions, add their help as well + for subaction in self._iter_indented_subactions(action): + parts.append(self._format_action(subaction)) + + # return a single string + return self._join_parts(parts) + + def _format_action_invocation(self, action): + if not action.option_strings: + metavar, = self._metavar_formatter(action, action.dest)(1) + return metavar + + else: + parts = [] + + # if the Optional doesn't take a value, format is: + # -s, --long + if action.nargs == 0: + parts.extend(action.option_strings) + + # if the Optional takes a value, format is: + # -s ARGS, --long ARGS + else: + default = action.dest.upper() + args_string = self._format_args(action, default) + for option_string in action.option_strings: + parts.append('%s %s' % (option_string, args_string)) + + return ', '.join(parts) + + def _metavar_formatter(self, action, default_metavar): + if action.metavar is not None: + result = action.metavar + elif action.choices is not None: + choice_strs = [str(choice) for choice in action.choices] + result = '{%s}' % ','.join(choice_strs) + else: + result = default_metavar + + def format(tuple_size): + if isinstance(result, tuple): + return result + else: + return (result, ) * tuple_size + return format + + def _format_args(self, action, default_metavar): + get_metavar = self._metavar_formatter(action, default_metavar) + if action.nargs is None: + result = '%s' % get_metavar(1) + elif action.nargs == OPTIONAL: + result = '[%s]' % get_metavar(1) + elif action.nargs == ZERO_OR_MORE: + result = '[%s [%s ...]]' % get_metavar(2) + elif action.nargs == ONE_OR_MORE: + result = '%s [%s ...]' % get_metavar(2) + elif action.nargs == REMAINDER: + result = '...' + elif action.nargs == PARSER: + result = '%s ...' % get_metavar(1) + else: + formats = ['%s' for _ in range(action.nargs)] + result = ' '.join(formats) % get_metavar(action.nargs) + return result + + def _expand_help(self, action): + params = dict(vars(action), prog=self._prog) + for name in list(params): + if params[name] is SUPPRESS: + del params[name] + for name in list(params): + if hasattr(params[name], '__name__'): + params[name] = params[name].__name__ + if params.get('choices') is not None: + choices_str = ', '.join([str(c) for c in params['choices']]) + params['choices'] = choices_str + return self._get_help_string(action) % params + + def _iter_indented_subactions(self, action): + try: + get_subactions = action._get_subactions + except AttributeError: + pass + else: + self._indent() + for subaction in get_subactions(): + yield subaction + self._dedent() + + def _split_lines(self, text, width): + text = self._whitespace_matcher.sub(' ', text).strip() + return _textwrap.wrap(text, width) + + def _fill_text(self, text, width, indent): + text = self._whitespace_matcher.sub(' ', text).strip() + return _textwrap.fill(text, width, initial_indent=indent, + subsequent_indent=indent) + + def _get_help_string(self, action): + return action.help + + +class RawDescriptionHelpFormatter(HelpFormatter): + """Help message formatter which retains any formatting in descriptions. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _fill_text(self, text, width, indent): + return ''.join([indent + line for line in text.splitlines(True)]) + + +class RawTextHelpFormatter(RawDescriptionHelpFormatter): + """Help message formatter which retains formatting of all help text. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _split_lines(self, text, width): + return text.splitlines() + + +class ArgumentDefaultsHelpFormatter(HelpFormatter): + """Help message formatter which adds default values to argument help. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _get_help_string(self, action): + help = action.help + if '%(default)' not in action.help: + if action.default is not SUPPRESS: + defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] + if action.option_strings or action.nargs in defaulting_nargs: + help += ' (default: %(default)s)' + return help + + +# ===================== +# Options and Arguments +# ===================== + +def _get_action_name(argument): + if argument is None: + return None + elif argument.option_strings: + return '/'.join(argument.option_strings) + elif argument.metavar not in (None, SUPPRESS): + return argument.metavar + elif argument.dest not in (None, SUPPRESS): + return argument.dest + else: + return None + + +class ArgumentError(Exception): + """An error from creating or using an argument (optional or positional). + + The string value of this exception is the message, augmented with + information about the argument that caused it. + """ + + def __init__(self, argument, message): + self.argument_name = _get_action_name(argument) + self.message = message + + def __str__(self): + if self.argument_name is None: + format = '%(message)s' + else: + format = 'argument %(argument_name)s: %(message)s' + return format % dict(message=self.message, + argument_name=self.argument_name) + + +class ArgumentTypeError(Exception): + """An error from trying to convert a command line string to a type.""" + pass + + +# ============== +# Action classes +# ============== + +class Action(_AttributeHolder): + """Information about how to convert command line strings to Python objects. + + Action objects are used by an ArgumentParser to represent the information + needed to parse a single argument from one or more strings from the + command line. The keyword arguments to the Action constructor are also + all attributes of Action instances. + + Keyword Arguments: + + - option_strings -- A list of command-line option strings which + should be associated with this action. + + - dest -- The name of the attribute to hold the created object(s) + + - nargs -- The number of command-line arguments that should be + consumed. By default, one argument will be consumed and a single + value will be produced. Other values include: + - N (an integer) consumes N arguments (and produces a list) + - '?' consumes zero or one arguments + - '*' consumes zero or more arguments (and produces a list) + - '+' consumes one or more arguments (and produces a list) + Note that the difference between the default and nargs=1 is that + with the default, a single value will be produced, while with + nargs=1, a list containing a single value will be produced. + + - const -- The value to be produced if the option is specified and the + option uses an action that takes no values. + + - default -- The value to be produced if the option is not specified. + + - type -- The type which the command-line arguments should be converted + to, should be one of 'string', 'int', 'float', 'complex' or a + callable object that accepts a single string argument. If None, + 'string' is assumed. + + - choices -- A container of values that should be allowed. If not None, + after a command-line argument has been converted to the appropriate + type, an exception will be raised if it is not a member of this + collection. + + - required -- True if the action must always be specified at the + command line. This is only meaningful for optional command-line + arguments. + + - help -- The help string describing the argument. + + - metavar -- The name to be used for the option's argument with the + help string. If None, the 'dest' value will be used as the name. + """ + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + self.option_strings = option_strings + self.dest = dest + self.nargs = nargs + self.const = const + self.default = default + self.type = type + self.choices = choices + self.required = required + self.help = help + self.metavar = metavar + + def _get_kwargs(self): + names = [ + 'option_strings', + 'dest', + 'nargs', + 'const', + 'default', + 'type', + 'choices', + 'help', + 'metavar', + ] + return [(name, getattr(self, name)) for name in names] + + def __call__(self, parser, namespace, values, option_string=None): + raise NotImplementedError(_('.__call__() not defined')) + + +class _StoreAction(Action): + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + if nargs == 0: + raise ValueError('nargs for store actions must be > 0; if you ' + 'have nothing to store, actions such as store ' + 'true or store const may be more appropriate') + if const is not None and nargs != OPTIONAL: + raise ValueError('nargs must be %r to supply const' % OPTIONAL) + super(_StoreAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=nargs, + const=const, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, values) + + +class _StoreConstAction(Action): + + def __init__(self, + option_strings, + dest, + const, + default=None, + required=False, + help=None, + metavar=None): + super(_StoreConstAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + const=const, + default=default, + required=required, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, self.const) + + +class _StoreTrueAction(_StoreConstAction): + + def __init__(self, + option_strings, + dest, + default=False, + required=False, + help=None): + super(_StoreTrueAction, self).__init__( + option_strings=option_strings, + dest=dest, + const=True, + default=default, + required=required, + help=help) + + +class _StoreFalseAction(_StoreConstAction): + + def __init__(self, + option_strings, + dest, + default=True, + required=False, + help=None): + super(_StoreFalseAction, self).__init__( + option_strings=option_strings, + dest=dest, + const=False, + default=default, + required=required, + help=help) + + +class _AppendAction(Action): + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + if nargs == 0: + raise ValueError('nargs for append actions must be > 0; if arg ' + 'strings are not supplying the value to append, ' + 'the append const action may be more appropriate') + if const is not None and nargs != OPTIONAL: + raise ValueError('nargs must be %r to supply const' % OPTIONAL) + super(_AppendAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=nargs, + const=const, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + items = _copy.copy(_ensure_value(namespace, self.dest, [])) + items.append(values) + setattr(namespace, self.dest, items) + + +class _AppendConstAction(Action): + + def __init__(self, + option_strings, + dest, + const, + default=None, + required=False, + help=None, + metavar=None): + super(_AppendConstAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + const=const, + default=default, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + items = _copy.copy(_ensure_value(namespace, self.dest, [])) + items.append(self.const) + setattr(namespace, self.dest, items) + + +class _CountAction(Action): + + def __init__(self, + option_strings, + dest, + default=None, + required=False, + help=None): + super(_CountAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + default=default, + required=required, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + new_count = _ensure_value(namespace, self.dest, 0) + 1 + setattr(namespace, self.dest, new_count) + + +class _HelpAction(Action): + + def __init__(self, + option_strings, + dest=SUPPRESS, + default=SUPPRESS, + help=None): + super(_HelpAction, self).__init__( + option_strings=option_strings, + dest=dest, + default=default, + nargs=0, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + parser.print_help() + parser.exit() + + +class _VersionAction(Action): + + def __init__(self, + option_strings, + version=None, + dest=SUPPRESS, + default=SUPPRESS, + help="show program's version number and exit"): + super(_VersionAction, self).__init__( + option_strings=option_strings, + dest=dest, + default=default, + nargs=0, + help=help) + self.version = version + + def __call__(self, parser, namespace, values, option_string=None): + version = self.version + if version is None: + version = parser.version + formatter = parser._get_formatter() + formatter.add_text(version) + parser.exit(message=formatter.format_help()) + + +class _SubParsersAction(Action): + + class _ChoicesPseudoAction(Action): + + def __init__(self, name, help): + sup = super(_SubParsersAction._ChoicesPseudoAction, self) + sup.__init__(option_strings=[], dest=name, help=help) + + def __init__(self, + option_strings, + prog, + parser_class, + dest=SUPPRESS, + help=None, + metavar=None): + + self._prog_prefix = prog + self._parser_class = parser_class + self._name_parser_map = {} + self._choices_actions = [] + + super(_SubParsersAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=PARSER, + choices=self._name_parser_map, + help=help, + metavar=metavar) + + def add_parser(self, name, **kwargs): + # set prog from the existing prefix + if kwargs.get('prog') is None: + kwargs['prog'] = '%s %s' % (self._prog_prefix, name) + + # create a pseudo-action to hold the choice help + if 'help' in kwargs: + help = kwargs.pop('help') + choice_action = self._ChoicesPseudoAction(name, help) + self._choices_actions.append(choice_action) + + # create the parser and add it to the map + parser = self._parser_class(**kwargs) + self._name_parser_map[name] = parser + return parser + + def _get_subactions(self): + return self._choices_actions + + def __call__(self, parser, namespace, values, option_string=None): + parser_name = values[0] + arg_strings = values[1:] + + # set the parser name if requested + if self.dest is not SUPPRESS: + setattr(namespace, self.dest, parser_name) + + # select the parser + try: + parser = self._name_parser_map[parser_name] + except KeyError: + tup = parser_name, ', '.join(self._name_parser_map) + msg = _('unknown parser %r (choices: %s)' % tup) + raise ArgumentError(self, msg) + + # parse all the remaining options into the namespace + # store any unrecognized options on the object, so that the top + # level parser can decide what to do with them + namespace, arg_strings = parser.parse_known_args(arg_strings, namespace) + if arg_strings: + vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, []) + getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings) + + +# ============== +# Type classes +# ============== + +class FileType(object): + """Factory for creating file object types + + Instances of FileType are typically passed as type= arguments to the + ArgumentParser add_argument() method. + + Keyword Arguments: + - mode -- A string indicating how the file is to be opened. Accepts the + same values as the builtin open() function. + - bufsize -- The file's desired buffer size. Accepts the same values as + the builtin open() function. + """ + + def __init__(self, mode='r', bufsize=None): + self._mode = mode + self._bufsize = bufsize + + def __call__(self, string): + # the special argument "-" means sys.std{in,out} + if string == '-': + if 'r' in self._mode: + return _sys.stdin + elif 'w' in self._mode: + return _sys.stdout + else: + msg = _('argument "-" with mode %r' % self._mode) + raise ValueError(msg) + + # all other arguments are used as file names + if self._bufsize: + return open(string, self._mode, self._bufsize) + else: + return open(string, self._mode) + + def __repr__(self): + args = [self._mode, self._bufsize] + args_str = ', '.join([repr(arg) for arg in args if arg is not None]) + return '%s(%s)' % (type(self).__name__, args_str) + +# =========================== +# Optional and Positional Parsing +# =========================== + +class Namespace(_AttributeHolder): + """Simple object for storing attributes. + + Implements equality by attribute names and values, and provides a simple + string representation. + """ + + def __init__(self, **kwargs): + for name in kwargs: + setattr(self, name, kwargs[name]) + + __hash__ = None + + def __eq__(self, other): + return vars(self) == vars(other) + + def __ne__(self, other): + return not (self == other) + + def __contains__(self, key): + return key in self.__dict__ + + +class _ActionsContainer(object): + + def __init__(self, + description, + prefix_chars, + argument_default, + conflict_handler): + super(_ActionsContainer, self).__init__() + + self.description = description + self.argument_default = argument_default + self.prefix_chars = prefix_chars + self.conflict_handler = conflict_handler + + # set up registries + self._registries = {} + + # register actions + self.register('action', None, _StoreAction) + self.register('action', 'store', _StoreAction) + self.register('action', 'store_const', _StoreConstAction) + self.register('action', 'store_true', _StoreTrueAction) + self.register('action', 'store_false', _StoreFalseAction) + self.register('action', 'append', _AppendAction) + self.register('action', 'append_const', _AppendConstAction) + self.register('action', 'count', _CountAction) + self.register('action', 'help', _HelpAction) + self.register('action', 'version', _VersionAction) + self.register('action', 'parsers', _SubParsersAction) + + # raise an exception if the conflict handler is invalid + self._get_handler() + + # action storage + self._actions = [] + self._option_string_actions = {} + + # groups + self._action_groups = [] + self._mutually_exclusive_groups = [] + + # defaults storage + self._defaults = {} + + # determines whether an "option" looks like a negative number + self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$') + + # whether or not there are any optionals that look like negative + # numbers -- uses a list so it can be shared and edited + self._has_negative_number_optionals = [] + + # ==================== + # Registration methods + # ==================== + def register(self, registry_name, value, object): + registry = self._registries.setdefault(registry_name, {}) + registry[value] = object + + def _registry_get(self, registry_name, value, default=None): + return self._registries[registry_name].get(value, default) + + # ================================== + # Namespace default accessor methods + # ================================== + def set_defaults(self, **kwargs): + self._defaults.update(kwargs) + + # if these defaults match any existing arguments, replace + # the previous default on the object with the new one + for action in self._actions: + if action.dest in kwargs: + action.default = kwargs[action.dest] + + def get_default(self, dest): + for action in self._actions: + if action.dest == dest and action.default is not None: + return action.default + return self._defaults.get(dest, None) + + + # ======================= + # Adding argument actions + # ======================= + def add_argument(self, *args, **kwargs): + """ + add_argument(dest, ..., name=value, ...) + add_argument(option_string, option_string, ..., name=value, ...) + """ + + # if no positional args are supplied or only one is supplied and + # it doesn't look like an option string, parse a positional + # argument + chars = self.prefix_chars + if not args or len(args) == 1 and args[0][0] not in chars: + if args and 'dest' in kwargs: + raise ValueError('dest supplied twice for positional argument') + kwargs = self._get_positional_kwargs(*args, **kwargs) + + # otherwise, we're adding an optional argument + else: + kwargs = self._get_optional_kwargs(*args, **kwargs) + + # if no default was supplied, use the parser-level default + if 'default' not in kwargs: + dest = kwargs['dest'] + if dest in self._defaults: + kwargs['default'] = self._defaults[dest] + elif self.argument_default is not None: + kwargs['default'] = self.argument_default + + # create the action object, and add it to the parser + action_class = self._pop_action_class(kwargs) + if not _callable(action_class): + raise ValueError('unknown action "%s"' % action_class) + action = action_class(**kwargs) + + # raise an error if the action type is not callable + type_func = self._registry_get('type', action.type, action.type) + if not _callable(type_func): + raise ValueError('%r is not callable' % type_func) + + return self._add_action(action) + + def add_argument_group(self, *args, **kwargs): + group = _ArgumentGroup(self, *args, **kwargs) + self._action_groups.append(group) + return group + + def add_mutually_exclusive_group(self, **kwargs): + group = _MutuallyExclusiveGroup(self, **kwargs) + self._mutually_exclusive_groups.append(group) + return group + + def _add_action(self, action): + # resolve any conflicts + self._check_conflict(action) + + # add to actions list + self._actions.append(action) + action.container = self + + # index the action by any option strings it has + for option_string in action.option_strings: + self._option_string_actions[option_string] = action + + # set the flag if any option strings look like negative numbers + for option_string in action.option_strings: + if self._negative_number_matcher.match(option_string): + if not self._has_negative_number_optionals: + self._has_negative_number_optionals.append(True) + + # return the created action + return action + + def _remove_action(self, action): + self._actions.remove(action) + + def _add_container_actions(self, container): + # collect groups by titles + title_group_map = {} + for group in self._action_groups: + if group.title in title_group_map: + msg = _('cannot merge actions - two groups are named %r') + raise ValueError(msg % (group.title)) + title_group_map[group.title] = group + + # map each action to its group + group_map = {} + for group in container._action_groups: + + # if a group with the title exists, use that, otherwise + # create a new group matching the container's group + if group.title not in title_group_map: + title_group_map[group.title] = self.add_argument_group( + title=group.title, + description=group.description, + conflict_handler=group.conflict_handler) + + # map the actions to their new group + for action in group._group_actions: + group_map[action] = title_group_map[group.title] + + # add container's mutually exclusive groups + # NOTE: if add_mutually_exclusive_group ever gains title= and + # description= then this code will need to be expanded as above + for group in container._mutually_exclusive_groups: + mutex_group = self.add_mutually_exclusive_group( + required=group.required) + + # map the actions to their new mutex group + for action in group._group_actions: + group_map[action] = mutex_group + + # add all actions to this container or their group + for action in container._actions: + group_map.get(action, self)._add_action(action) + + def _get_positional_kwargs(self, dest, **kwargs): + # make sure required is not specified + if 'required' in kwargs: + msg = _("'required' is an invalid argument for positionals") + raise TypeError(msg) + + # mark positional arguments as required if at least one is + # always required + if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]: + kwargs['required'] = True + if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs: + kwargs['required'] = True + + # return the keyword arguments with no option strings + return dict(kwargs, dest=dest, option_strings=[]) + + def _get_optional_kwargs(self, *args, **kwargs): + # determine short and long option strings + option_strings = [] + long_option_strings = [] + for option_string in args: + # error on strings that don't start with an appropriate prefix + if not option_string[0] in self.prefix_chars: + msg = _('invalid option string %r: ' + 'must start with a character %r') + tup = option_string, self.prefix_chars + raise ValueError(msg % tup) + + # strings starting with two prefix characters are long options + option_strings.append(option_string) + if option_string[0] in self.prefix_chars: + if len(option_string) > 1: + if option_string[1] in self.prefix_chars: + long_option_strings.append(option_string) + + # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' + dest = kwargs.pop('dest', None) + if dest is None: + if long_option_strings: + dest_option_string = long_option_strings[0] + else: + dest_option_string = option_strings[0] + dest = dest_option_string.lstrip(self.prefix_chars) + if not dest: + msg = _('dest= is required for options like %r') + raise ValueError(msg % option_string) + dest = dest.replace('-', '_') + + # return the updated keyword arguments + return dict(kwargs, dest=dest, option_strings=option_strings) + + def _pop_action_class(self, kwargs, default=None): + action = kwargs.pop('action', default) + return self._registry_get('action', action, action) + + def _get_handler(self): + # determine function from conflict handler string + handler_func_name = '_handle_conflict_%s' % self.conflict_handler + try: + return getattr(self, handler_func_name) + except AttributeError: + msg = _('invalid conflict_resolution value: %r') + raise ValueError(msg % self.conflict_handler) + + def _check_conflict(self, action): + + # find all options that conflict with this option + confl_optionals = [] + for option_string in action.option_strings: + if option_string in self._option_string_actions: + confl_optional = self._option_string_actions[option_string] + confl_optionals.append((option_string, confl_optional)) + + # resolve any conflicts + if confl_optionals: + conflict_handler = self._get_handler() + conflict_handler(action, confl_optionals) + + def _handle_conflict_error(self, action, conflicting_actions): + message = _('conflicting option string(s): %s') + conflict_string = ', '.join([option_string + for option_string, action + in conflicting_actions]) + raise ArgumentError(action, message % conflict_string) + + def _handle_conflict_resolve(self, action, conflicting_actions): + + # remove all conflicting options + for option_string, action in conflicting_actions: + + # remove the conflicting option + action.option_strings.remove(option_string) + self._option_string_actions.pop(option_string, None) + + # if the option now has no option string, remove it from the + # container holding it + if not action.option_strings: + action.container._remove_action(action) + + +class _ArgumentGroup(_ActionsContainer): + + def __init__(self, container, title=None, description=None, **kwargs): + # add any missing keyword arguments by checking the container + update = kwargs.setdefault + update('conflict_handler', container.conflict_handler) + update('prefix_chars', container.prefix_chars) + update('argument_default', container.argument_default) + super_init = super(_ArgumentGroup, self).__init__ + super_init(description=description, **kwargs) + + # group attributes + self.title = title + self._group_actions = [] + + # share most attributes with the container + self._registries = container._registries + self._actions = container._actions + self._option_string_actions = container._option_string_actions + self._defaults = container._defaults + self._has_negative_number_optionals = \ + container._has_negative_number_optionals + + def _add_action(self, action): + action = super(_ArgumentGroup, self)._add_action(action) + self._group_actions.append(action) + return action + + def _remove_action(self, action): + super(_ArgumentGroup, self)._remove_action(action) + self._group_actions.remove(action) + + +class _MutuallyExclusiveGroup(_ArgumentGroup): + + def __init__(self, container, required=False): + super(_MutuallyExclusiveGroup, self).__init__(container) + self.required = required + self._container = container + + def _add_action(self, action): + if action.required: + msg = _('mutually exclusive arguments must be optional') + raise ValueError(msg) + action = self._container._add_action(action) + self._group_actions.append(action) + return action + + def _remove_action(self, action): + self._container._remove_action(action) + self._group_actions.remove(action) + + +class ArgumentParser(_AttributeHolder, _ActionsContainer): + """Object for parsing command line strings into Python objects. + + Keyword Arguments: + - prog -- The name of the program (default: sys.argv[0]) + - usage -- A usage message (default: auto-generated from arguments) + - description -- A description of what the program does + - epilog -- Text following the argument descriptions + - parents -- Parsers whose arguments should be copied into this one + - formatter_class -- HelpFormatter class for printing help messages + - prefix_chars -- Characters that prefix optional arguments + - fromfile_prefix_chars -- Characters that prefix files containing + additional arguments + - argument_default -- The default value for all arguments + - conflict_handler -- String indicating how to handle conflicts + - add_help -- Add a -h/-help option + """ + + def __init__(self, + prog=None, + usage=None, + description=None, + epilog=None, + version=None, + parents=[], + formatter_class=HelpFormatter, + prefix_chars='-', + fromfile_prefix_chars=None, + argument_default=None, + conflict_handler='error', + add_help=True): + + if version is not None: + import warnings + warnings.warn( + """The "version" argument to ArgumentParser is deprecated. """ + """Please use """ + """"add_argument(..., action='version', version="N", ...)" """ + """instead""", DeprecationWarning) + + superinit = super(ArgumentParser, self).__init__ + superinit(description=description, + prefix_chars=prefix_chars, + argument_default=argument_default, + conflict_handler=conflict_handler) + + # default setting for prog + if prog is None: + prog = _os.path.basename(_sys.argv[0]) + + self.prog = prog + self.usage = usage + self.epilog = epilog + self.version = version + self.formatter_class = formatter_class + self.fromfile_prefix_chars = fromfile_prefix_chars + self.add_help = add_help + + add_group = self.add_argument_group + self._positionals = add_group(_('positional arguments')) + self._optionals = add_group(_('optional arguments')) + self._subparsers = None + + # register types + def identity(string): + return string + self.register('type', None, identity) + + # add help and version arguments if necessary + # (using explicit default to override global argument_default) + if '-' in prefix_chars: + default_prefix = '-' + else: + default_prefix = prefix_chars[0] + if self.add_help: + self.add_argument( + default_prefix+'h', default_prefix*2+'help', + action='help', default=SUPPRESS, + help=_('show this help message and exit')) + if self.version: + self.add_argument( + default_prefix+'v', default_prefix*2+'version', + action='version', default=SUPPRESS, + version=self.version, + help=_("show program's version number and exit")) + + # add parent arguments and defaults + for parent in parents: + self._add_container_actions(parent) + try: + defaults = parent._defaults + except AttributeError: + pass + else: + self._defaults.update(defaults) + + # ======================= + # Pretty __repr__ methods + # ======================= + def _get_kwargs(self): + names = [ + 'prog', + 'usage', + 'description', + 'version', + 'formatter_class', + 'conflict_handler', + 'add_help', + ] + return [(name, getattr(self, name)) for name in names] + + # ================================== + # Optional/Positional adding methods + # ================================== + def add_subparsers(self, **kwargs): + if self._subparsers is not None: + self.error(_('cannot have multiple subparser arguments')) + + # add the parser class to the arguments if it's not present + kwargs.setdefault('parser_class', type(self)) + + if 'title' in kwargs or 'description' in kwargs: + title = _(kwargs.pop('title', 'subcommands')) + description = _(kwargs.pop('description', None)) + self._subparsers = self.add_argument_group(title, description) + else: + self._subparsers = self._positionals + + # prog defaults to the usage message of this parser, skipping + # optional arguments and with no "usage:" prefix + if kwargs.get('prog') is None: + formatter = self._get_formatter() + positionals = self._get_positional_actions() + groups = self._mutually_exclusive_groups + formatter.add_usage(self.usage, positionals, groups, '') + kwargs['prog'] = formatter.format_help().strip() + + # create the parsers action and add it to the positionals list + parsers_class = self._pop_action_class(kwargs, 'parsers') + action = parsers_class(option_strings=[], **kwargs) + self._subparsers._add_action(action) + + # return the created parsers action + return action + + def _add_action(self, action): + if action.option_strings: + self._optionals._add_action(action) + else: + self._positionals._add_action(action) + return action + + def _get_optional_actions(self): + return [action + for action in self._actions + if action.option_strings] + + def _get_positional_actions(self): + return [action + for action in self._actions + if not action.option_strings] + + # ===================================== + # Command line argument parsing methods + # ===================================== + def parse_args(self, args=None, namespace=None): + args, argv = self.parse_known_args(args, namespace) + if argv: + msg = _('unrecognized arguments: %s') + self.error(msg % ' '.join(argv)) + return args + + def parse_known_args(self, args=None, namespace=None): + # args default to the system args + if args is None: + args = _sys.argv[1:] + + # default Namespace built from parser defaults + if namespace is None: + namespace = Namespace() + + # add any action defaults that aren't present + for action in self._actions: + if action.dest is not SUPPRESS: + if not hasattr(namespace, action.dest): + if action.default is not SUPPRESS: + default = action.default + if isinstance(action.default, basestring): + default = self._get_value(action, default) + setattr(namespace, action.dest, default) + + # add any parser defaults that aren't present + for dest in self._defaults: + if not hasattr(namespace, dest): + setattr(namespace, dest, self._defaults[dest]) + + # parse the arguments and exit if there are any errors + try: + namespace, args = self._parse_known_args(args, namespace) + if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR): + args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) + delattr(namespace, _UNRECOGNIZED_ARGS_ATTR) + return namespace, args + except ArgumentError: + err = _sys.exc_info()[1] + self.error(str(err)) + + def _parse_known_args(self, arg_strings, namespace): + # replace arg strings that are file references + if self.fromfile_prefix_chars is not None: + arg_strings = self._read_args_from_files(arg_strings) + + # map all mutually exclusive arguments to the other arguments + # they can't occur with + action_conflicts = {} + for mutex_group in self._mutually_exclusive_groups: + group_actions = mutex_group._group_actions + for i, mutex_action in enumerate(mutex_group._group_actions): + conflicts = action_conflicts.setdefault(mutex_action, []) + conflicts.extend(group_actions[:i]) + conflicts.extend(group_actions[i + 1:]) + + # find all option indices, and determine the arg_string_pattern + # which has an 'O' if there is an option at an index, + # an 'A' if there is an argument, or a '-' if there is a '--' + option_string_indices = {} + arg_string_pattern_parts = [] + arg_strings_iter = iter(arg_strings) + for i, arg_string in enumerate(arg_strings_iter): + + # all args after -- are non-options + if arg_string == '--': + arg_string_pattern_parts.append('-') + for arg_string in arg_strings_iter: + arg_string_pattern_parts.append('A') + + # otherwise, add the arg to the arg strings + # and note the index if it was an option + else: + option_tuple = self._parse_optional(arg_string) + if option_tuple is None: + pattern = 'A' + else: + option_string_indices[i] = option_tuple + pattern = 'O' + arg_string_pattern_parts.append(pattern) + + # join the pieces together to form the pattern + arg_strings_pattern = ''.join(arg_string_pattern_parts) + + # converts arg strings to the appropriate and then takes the action + seen_actions = set() + seen_non_default_actions = set() + + def take_action(action, argument_strings, option_string=None): + seen_actions.add(action) + argument_values = self._get_values(action, argument_strings) + + # error if this argument is not allowed with other previously + # seen arguments, assuming that actions that use the default + # value don't really count as "present" + if argument_values is not action.default: + seen_non_default_actions.add(action) + for conflict_action in action_conflicts.get(action, []): + if conflict_action in seen_non_default_actions: + msg = _('not allowed with argument %s') + action_name = _get_action_name(conflict_action) + raise ArgumentError(action, msg % action_name) + + # take the action if we didn't receive a SUPPRESS value + # (e.g. from a default) + if argument_values is not SUPPRESS: + action(self, namespace, argument_values, option_string) + + # function to convert arg_strings into an optional action + def consume_optional(start_index): + + # get the optional identified at this index + option_tuple = option_string_indices[start_index] + action, option_string, explicit_arg = option_tuple + + # identify additional optionals in the same arg string + # (e.g. -xyz is the same as -x -y -z if no args are required) + match_argument = self._match_argument + action_tuples = [] + while True: + + # if we found no optional action, skip it + if action is None: + extras.append(arg_strings[start_index]) + return start_index + 1 + + # if there is an explicit argument, try to match the + # optional's string arguments to only this + if explicit_arg is not None: + arg_count = match_argument(action, 'A') + + # if the action is a single-dash option and takes no + # arguments, try to parse more single-dash options out + # of the tail of the option string + chars = self.prefix_chars + if arg_count == 0 and option_string[1] not in chars: + action_tuples.append((action, [], option_string)) + char = option_string[0] + option_string = char + explicit_arg[0] + new_explicit_arg = explicit_arg[1:] or None + optionals_map = self._option_string_actions + if option_string in optionals_map: + action = optionals_map[option_string] + explicit_arg = new_explicit_arg + else: + msg = _('ignored explicit argument %r') + raise ArgumentError(action, msg % explicit_arg) + + # if the action expect exactly one argument, we've + # successfully matched the option; exit the loop + elif arg_count == 1: + stop = start_index + 1 + args = [explicit_arg] + action_tuples.append((action, args, option_string)) + break + + # error if a double-dash option did not use the + # explicit argument + else: + msg = _('ignored explicit argument %r') + raise ArgumentError(action, msg % explicit_arg) + + # if there is no explicit argument, try to match the + # optional's string arguments with the following strings + # if successful, exit the loop + else: + start = start_index + 1 + selected_patterns = arg_strings_pattern[start:] + arg_count = match_argument(action, selected_patterns) + stop = start + arg_count + args = arg_strings[start:stop] + action_tuples.append((action, args, option_string)) + break + + # add the Optional to the list and return the index at which + # the Optional's string args stopped + assert action_tuples + for action, args, option_string in action_tuples: + take_action(action, args, option_string) + return stop + + # the list of Positionals left to be parsed; this is modified + # by consume_positionals() + positionals = self._get_positional_actions() + + # function to convert arg_strings into positional actions + def consume_positionals(start_index): + # match as many Positionals as possible + match_partial = self._match_arguments_partial + selected_pattern = arg_strings_pattern[start_index:] + arg_counts = match_partial(positionals, selected_pattern) + + # slice off the appropriate arg strings for each Positional + # and add the Positional and its args to the list + for action, arg_count in zip(positionals, arg_counts): + args = arg_strings[start_index: start_index + arg_count] + start_index += arg_count + take_action(action, args) + + # slice off the Positionals that we just parsed and return the + # index at which the Positionals' string args stopped + positionals[:] = positionals[len(arg_counts):] + return start_index + + # consume Positionals and Optionals alternately, until we have + # passed the last option string + extras = [] + start_index = 0 + if option_string_indices: + max_option_string_index = max(option_string_indices) + else: + max_option_string_index = -1 + while start_index <= max_option_string_index: + + # consume any Positionals preceding the next option + next_option_string_index = min([ + index + for index in option_string_indices + if index >= start_index]) + if start_index != next_option_string_index: + positionals_end_index = consume_positionals(start_index) + + # only try to parse the next optional if we didn't consume + # the option string during the positionals parsing + if positionals_end_index > start_index: + start_index = positionals_end_index + continue + else: + start_index = positionals_end_index + + # if we consumed all the positionals we could and we're not + # at the index of an option string, there were extra arguments + if start_index not in option_string_indices: + strings = arg_strings[start_index:next_option_string_index] + extras.extend(strings) + start_index = next_option_string_index + + # consume the next optional and any arguments for it + start_index = consume_optional(start_index) + + # consume any positionals following the last Optional + stop_index = consume_positionals(start_index) + + # if we didn't consume all the argument strings, there were extras + extras.extend(arg_strings[stop_index:]) + + # if we didn't use all the Positional objects, there were too few + # arg strings supplied. + if positionals: + self.error(_('too few arguments')) + + # make sure all required actions were present + for action in self._actions: + if action.required: + if action not in seen_actions: + name = _get_action_name(action) + self.error(_('argument %s is required') % name) + + # make sure all required groups had one option present + for group in self._mutually_exclusive_groups: + if group.required: + for action in group._group_actions: + if action in seen_non_default_actions: + break + + # if no actions were used, report the error + else: + names = [_get_action_name(action) + for action in group._group_actions + if action.help is not SUPPRESS] + msg = _('one of the arguments %s is required') + self.error(msg % ' '.join(names)) + + # return the updated namespace and the extra arguments + return namespace, extras + + def _read_args_from_files(self, arg_strings): + # expand arguments referencing files + new_arg_strings = [] + for arg_string in arg_strings: + + # for regular arguments, just add them back into the list + if arg_string[0] not in self.fromfile_prefix_chars: + new_arg_strings.append(arg_string) + + # replace arguments referencing files with the file content + else: + try: + args_file = open(arg_string[1:]) + try: + arg_strings = [] + for arg_line in args_file.read().splitlines(): + for arg in self.convert_arg_line_to_args(arg_line): + arg_strings.append(arg) + arg_strings = self._read_args_from_files(arg_strings) + new_arg_strings.extend(arg_strings) + finally: + args_file.close() + except IOError: + err = _sys.exc_info()[1] + self.error(str(err)) + + # return the modified argument list + return new_arg_strings + + def convert_arg_line_to_args(self, arg_line): + return [arg_line] + + def _match_argument(self, action, arg_strings_pattern): + # match the pattern for this action to the arg strings + nargs_pattern = self._get_nargs_pattern(action) + match = _re.match(nargs_pattern, arg_strings_pattern) + + # raise an exception if we weren't able to find a match + if match is None: + nargs_errors = { + None: _('expected one argument'), + OPTIONAL: _('expected at most one argument'), + ONE_OR_MORE: _('expected at least one argument'), + } + default = _('expected %s argument(s)') % action.nargs + msg = nargs_errors.get(action.nargs, default) + raise ArgumentError(action, msg) + + # return the number of arguments matched + return len(match.group(1)) + + def _match_arguments_partial(self, actions, arg_strings_pattern): + # progressively shorten the actions list by slicing off the + # final actions until we find a match + result = [] + for i in range(len(actions), 0, -1): + actions_slice = actions[:i] + pattern = ''.join([self._get_nargs_pattern(action) + for action in actions_slice]) + match = _re.match(pattern, arg_strings_pattern) + if match is not None: + result.extend([len(string) for string in match.groups()]) + break + + # return the list of arg string counts + return result + + def _parse_optional(self, arg_string): + # if it's an empty string, it was meant to be a positional + if not arg_string: + return None + + # if it doesn't start with a prefix, it was meant to be positional + if not arg_string[0] in self.prefix_chars: + return None + + # if the option string is present in the parser, return the action + if arg_string in self._option_string_actions: + action = self._option_string_actions[arg_string] + return action, arg_string, None + + # if it's just a single character, it was meant to be positional + if len(arg_string) == 1: + return None + + # if the option string before the "=" is present, return the action + if '=' in arg_string: + option_string, explicit_arg = arg_string.split('=', 1) + if option_string in self._option_string_actions: + action = self._option_string_actions[option_string] + return action, option_string, explicit_arg + + # search through all possible prefixes of the option string + # and all actions in the parser for possible interpretations + option_tuples = self._get_option_tuples(arg_string) + + # if multiple actions match, the option string was ambiguous + if len(option_tuples) > 1: + options = ', '.join([option_string + for action, option_string, explicit_arg in option_tuples]) + tup = arg_string, options + self.error(_('ambiguous option: %s could match %s') % tup) + + # if exactly one action matched, this segmentation is good, + # so return the parsed action + elif len(option_tuples) == 1: + option_tuple, = option_tuples + return option_tuple + + # if it was not found as an option, but it looks like a negative + # number, it was meant to be positional + # unless there are negative-number-like options + if self._negative_number_matcher.match(arg_string): + if not self._has_negative_number_optionals: + return None + + # if it contains a space, it was meant to be a positional + if ' ' in arg_string: + return None + + # it was meant to be an optional but there is no such option + # in this parser (though it might be a valid option in a subparser) + return None, arg_string, None + + def _get_option_tuples(self, option_string): + result = [] + + # option strings starting with two prefix characters are only + # split at the '=' + chars = self.prefix_chars + if option_string[0] in chars and option_string[1] in chars: + if '=' in option_string: + option_prefix, explicit_arg = option_string.split('=', 1) + else: + option_prefix = option_string + explicit_arg = None + for option_string in self._option_string_actions: + if option_string.startswith(option_prefix): + action = self._option_string_actions[option_string] + tup = action, option_string, explicit_arg + result.append(tup) + + # single character options can be concatenated with their arguments + # but multiple character options always have to have their argument + # separate + elif option_string[0] in chars and option_string[1] not in chars: + option_prefix = option_string + explicit_arg = None + short_option_prefix = option_string[:2] + short_explicit_arg = option_string[2:] + + for option_string in self._option_string_actions: + if option_string == short_option_prefix: + action = self._option_string_actions[option_string] + tup = action, option_string, short_explicit_arg + result.append(tup) + elif option_string.startswith(option_prefix): + action = self._option_string_actions[option_string] + tup = action, option_string, explicit_arg + result.append(tup) + + # shouldn't ever get here + else: + self.error(_('unexpected option string: %s') % option_string) + + # return the collected option tuples + return result + + def _get_nargs_pattern(self, action): + # in all examples below, we have to allow for '--' args + # which are represented as '-' in the pattern + nargs = action.nargs + + # the default (None) is assumed to be a single argument + if nargs is None: + nargs_pattern = '(-*A-*)' + + # allow zero or one arguments + elif nargs == OPTIONAL: + nargs_pattern = '(-*A?-*)' + + # allow zero or more arguments + elif nargs == ZERO_OR_MORE: + nargs_pattern = '(-*[A-]*)' + + # allow one or more arguments + elif nargs == ONE_OR_MORE: + nargs_pattern = '(-*A[A-]*)' + + # allow any number of options or arguments + elif nargs == REMAINDER: + nargs_pattern = '([-AO]*)' + + # allow one argument followed by any number of options or arguments + elif nargs == PARSER: + nargs_pattern = '(-*A[-AO]*)' + + # all others should be integers + else: + nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs) + + # if this is an optional action, -- is not allowed + if action.option_strings: + nargs_pattern = nargs_pattern.replace('-*', '') + nargs_pattern = nargs_pattern.replace('-', '') + + # return the pattern + return nargs_pattern + + # ======================== + # Value conversion methods + # ======================== + def _get_values(self, action, arg_strings): + # for everything but PARSER args, strip out '--' + if action.nargs not in [PARSER, REMAINDER]: + arg_strings = [s for s in arg_strings if s != '--'] + + # optional argument produces a default when not present + if not arg_strings and action.nargs == OPTIONAL: + if action.option_strings: + value = action.const + else: + value = action.default + if isinstance(value, basestring): + value = self._get_value(action, value) + self._check_value(action, value) + + # when nargs='*' on a positional, if there were no command-line + # args, use the default if it is anything other than None + elif (not arg_strings and action.nargs == ZERO_OR_MORE and + not action.option_strings): + if action.default is not None: + value = action.default + else: + value = arg_strings + self._check_value(action, value) + + # single argument or optional argument produces a single value + elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]: + arg_string, = arg_strings + value = self._get_value(action, arg_string) + self._check_value(action, value) + + # REMAINDER arguments convert all values, checking none + elif action.nargs == REMAINDER: + value = [self._get_value(action, v) for v in arg_strings] + + # PARSER arguments convert all values, but check only the first + elif action.nargs == PARSER: + value = [self._get_value(action, v) for v in arg_strings] + self._check_value(action, value[0]) + + # all other types of nargs produce a list + else: + value = [self._get_value(action, v) for v in arg_strings] + for v in value: + self._check_value(action, v) + + # return the converted value + return value + + def _get_value(self, action, arg_string): + type_func = self._registry_get('type', action.type, action.type) + if not _callable(type_func): + msg = _('%r is not callable') + raise ArgumentError(action, msg % type_func) + + # convert the value to the appropriate type + try: + result = type_func(arg_string) + + # ArgumentTypeErrors indicate errors + except ArgumentTypeError: + name = getattr(action.type, '__name__', repr(action.type)) + msg = str(_sys.exc_info()[1]) + raise ArgumentError(action, msg) + + # TypeErrors or ValueErrors also indicate errors + except (TypeError, ValueError): + name = getattr(action.type, '__name__', repr(action.type)) + msg = _('invalid %s value: %r') + raise ArgumentError(action, msg % (name, arg_string)) + + # return the converted value + return result + + def _check_value(self, action, value): + # converted value must be one of the choices (if specified) + if action.choices is not None and value not in action.choices: + tup = value, ', '.join(map(repr, action.choices)) + msg = _('invalid choice: %r (choose from %s)') % tup + raise ArgumentError(action, msg) + + # ======================= + # Help-formatting methods + # ======================= + def format_usage(self): + formatter = self._get_formatter() + formatter.add_usage(self.usage, self._actions, + self._mutually_exclusive_groups) + return formatter.format_help() + + def format_help(self): + formatter = self._get_formatter() + + # usage + formatter.add_usage(self.usage, self._actions, + self._mutually_exclusive_groups) + + # description + formatter.add_text(self.description) + + # positionals, optionals and user-defined groups + for action_group in self._action_groups: + formatter.start_section(action_group.title) + formatter.add_text(action_group.description) + formatter.add_arguments(action_group._group_actions) + formatter.end_section() + + # epilog + formatter.add_text(self.epilog) + + # determine help from format above + return formatter.format_help() + + def format_version(self): + import warnings + warnings.warn( + 'The format_version method is deprecated -- the "version" ' + 'argument to ArgumentParser is no longer supported.', + DeprecationWarning) + formatter = self._get_formatter() + formatter.add_text(self.version) + return formatter.format_help() + + def _get_formatter(self): + return self.formatter_class(prog=self.prog) + + # ===================== + # Help-printing methods + # ===================== + def print_usage(self, file=None): + if file is None: + file = _sys.stdout + self._print_message(self.format_usage(), file) + + def print_help(self, file=None): + if file is None: + file = _sys.stdout + self._print_message(self.format_help(), file) + + def print_version(self, file=None): + import warnings + warnings.warn( + 'The print_version method is deprecated -- the "version" ' + 'argument to ArgumentParser is no longer supported.', + DeprecationWarning) + self._print_message(self.format_version(), file) + + def _print_message(self, message, file=None): + if message: + if file is None: + file = _sys.stderr + file.write(message) + + # =============== + # Exiting methods + # =============== + def exit(self, status=0, message=None): + if message: + self._print_message(message, _sys.stderr) + _sys.exit(status) + + def error(self, message): + """error(message: string) + + Prints a usage message incorporating the message to stderr and + exits. + + If you override this in a subclass, it should not return -- it + should either exit or raise an exception. + """ + self.print_usage(_sys.stderr) + self.exit(2, _('%s: error: %s\n') % (self.prog, message)) diff --git a/.config/help b/.config/help new file mode 100755 index 0000000..42707d2 --- /dev/null +++ b/.config/help @@ -0,0 +1,54 @@ +#!/usr/bin/python + +import re, os, sys, textwrap +# Formats help message +# $1 - help type: var, target or example + +text = sys.stdin.read() + +def fmt_2cols(name): + global text + + text = re.sub('(?mi)\s*\n\s+', ' ', text) + text = [ t for t in text.split('\n') if t ] + cols = [] + for t in text: + try: + (n, m) = t.split(' - ', 1) + cols.append((n.strip(), m.strip())) + except: + pass + + m = max([ len(e[0]) for e in cols ]) + ind = ' ' * m + text = "" + for c in cols: + help = textwrap.fill(c[1], initial_indent = ind, + subsequent_indent = ind) + text += ' {0:>{1}s} - {2}\n'.format(c[0],m, help.strip()) + text.strip() + + if text: + print "%s:\n%s" % (name, text) + + +def fmt_pre(): + tok = text.split('@@\n') + tok2 = [] + for t in tok: + if t.strip(): + tok2.append(t.strip()) + + for i, t in enumerate(tok2): + print 'Example %d:' % (i+1) + print t + print + +if sys.argv[1] == 'variable': + fmt_2cols('Variables') +elif sys.argv[1] == 'target': + fmt_2cols('Targets') +elif sys.argv[1] == 'example': + fmt_pre() +else: + raise Exception, 'Unknown arg: ' + sys.argv[1] diff --git a/.config/options.py b/.config/options.py new file mode 100644 index 0000000..885ef80 --- /dev/null +++ b/.config/options.py @@ -0,0 +1,100 @@ + +# Initialization. Create your opts here. Runs before command line processing. +def init(): + # standart autoconf options + opt_group_new('autoconf', 'Standart autoconf options') + opt_new("prefix", group = 'autoconf', + help = "install architecture-independent files", metavar='DIR', + default = lambda : '/usr') + opt_new("eprefix", group = 'autoconf', + help = "install architecture-dependent files", metavar='DIR', + default = lambda : opt('prefix')) + opt_new("bindir", group = 'autoconf', + help = "user executables", metavar='DIR', + default = lambda : opt('eprefix') + '/bin') + opt_new("sbindir", group = 'autoconf', + help = "system executables", metavar='DIR', + default = lambda : opt('eprefix') + '/sbin') + opt_new("libexecdir", group = 'autoconf', + help = "program executables", metavar='DIR', + default = lambda : opt('eprefix') + '/libexec/' + opt('project_name')) + opt_new("libdir", group = 'autoconf', + help = "object code libraries", metavar='DIR', + default = lambda : opt('eprefix') + '/lib/' + opt('project_name')) + opt_new("sysconfdir", group = 'autoconf', + help = "read-only single-machine data", metavar='DIR', + default = lambda : opt('prefix') + '/etc') + opt_new("datadir", group = 'autoconf', + help = "read-only architecture-independent data", metavar='DIR', + default = lambda : opt('prefix') + '/share/' + opt('project_name')) + opt_new("localedir", group = 'autoconf', + help = "languagetranslation files", metavar='DIR', + default = lambda : opt('datadir') + '/locale') + opt_new("includedir", group = 'autoconf', + help = "C header files", metavar='DIR', + default = lambda : opt('prefix') + '/include') + opt_new("mandir", group = 'autoconf', + help = "man documentation", metavar='DIR', + default = lambda : opt('prefix') + '/man') + opt_new("infodir", group = 'autoconf', + help = "info documentation", metavar='DIR', + default = lambda : opt('prefix') + '/info') + opt_new("localstatedir", group = 'autoconf', + help = "modifiable single-machine data in DIR", metavar='DIR', + default = lambda : opt('prefix') + '/var') + + opt_group_new('app', 'Application options', default = True) + opt_new("project_name", group = 'app', + help = "Project name", metavar='NAME', + default = 'fbpanel') + opt_new("project_version", group = 'app', + help = "Project version", metavar='VER', + default = lambda : detect_project_version()) + opt_new("sound", group = 'app', + help = "Enable sound plugin (default: autodetect)", + action = ToggleAction, default = None) + +# Logic and stuff that does not require human interaction. Runs after +# command line processing. +# Here you can do anything you want. You can create opts, delete opts, modify +# their values. Just bear in mind: whatever you do here will end up in +# config.h. +def resolve(): + # If user did not set sound on command line, it is autodetected. + if opt('sound') is None and pkg_exists('alsa', '--atleast-version=1.0.10'): + opt_set('sound', True) + + # alsa is required, only if "sound" is enabled. + if opt('sound'): + # if alsa is not installed, will raise exception + opt_new_from_pkg('alsa', 'alsa', pversion = '--atleast-version=1.0.10') + + opt_new_from_pkg('gtk2', 'gtk+-2.0', pversion = '--atleast-version=2.17') + opt_new_from_pkg('gmodule2', 'gmodule-2.0') + opt_new_from_pkg('x11', 'x11') + opt_new('cflags_extra', default='-I$(TOPDIR)/panel') + +def detect_project_name(): + # Hardcode here the name of your project + # ret = "projectname" + + # Alternatively, take top dir name as a project name + ret = os.getcwd().split('/')[-1] + + return ret + +def detect_project_version(): + ret = open('version', 'r').read().strip() + + return ret + +# Give a summary on created configuration +def report(): + str = "Configuration:\n" + str += " Sound plugin: " + if opt('sound'): + str += "yes\n" + else: + str += "no\n" + print str, + diff --git a/.config/repl.py b/.config/repl.py new file mode 100755 index 0000000..2194600 --- /dev/null +++ b/.config/repl.py @@ -0,0 +1,18 @@ +#!/usr/bin/python + +import re, sys + +repl_dict = { +#repl_dict# +} + +def repl_func(matchobj): + key = matchobj.group(0)[1:-1] + if key in repl_dict: + return repl_dict[key] + else: + return matchobj.group(0) + + +print re.sub('@\w+@', repl_func, sys.stdin.read()) + diff --git a/.config/rules.mk b/.config/rules.mk new file mode 100644 index 0000000..79e4983 --- /dev/null +++ b/.config/rules.mk @@ -0,0 +1,326 @@ + +############################################### +# environment checks # +############################################### + +SHELL=/bin/bash + +.DEFAULT_GOAL := all + +define prnvar +$(warning $(origin $1) $1='$($1)') +endef + +$(TOPDIR)/config.mk: $(TOPDIR)/version $(TOPDIR)/.config/* + @echo Please run $(TOPDIR)/configure + @echo + @false +include $(TOPDIR)/config.mk + + +ifeq ($(realpath $(TOPDIR)),$(CURDIR)) +IS_TOPDIR := yes +else +IS_TOPDIR := no +endif + +IS_ALL := $(filter all,$(if $(MAKECMDGOALS),$(MAKECMDGOALS),$(.DEFAULT_GOAL))) +IS_HELP := $(filter help,$(MAKECMDGOALS)) + +ifeq ($(NV),) +export NV = $(PROJECT_NAME)-$(PROJECT_VERSION) +endif + +EMPTY := +SPACE := $(EMPTY) $(EMPTY) + +############################################### +# recurion rules # +############################################### + +RGOALS = all clean install svnignore +.PHONY : $(RGOALS) $(SUBDIRS) +$(RGOALS) : $(SUBDIRS) + +$(SUBDIRS): + $Q$(MAKE) -S -C $@ $(MAKECMDGOALS) +unexport SUBDIRS + + +$(RGOALS) $(SUBDIRS) : FORCE +FORCE: + @# + + +############################################### +# help # +############################################### + +# Help targets allow to shed a light on what your makefile does. +# Text for targets and variables will be formated into nice columns, +# while help for examples will be printed as is. +# +# You can have any number of these, if you use '::' synatx +# +# The syntax is +# help_target :: +# echo "name - explanation, every" +# echo " next line is indented" +# +# help_variable :: +# echo "name - explanation, every" +# echo " next line is indented" +# +# help_example :: +# echo "@@" +# echo "Any multi-line text formated as desired" + +help : + $Q$(MAKE) -j1 V=0 help_target | $(TOPDIR)/.config/help target + $Q$(MAKE) -j1 V=0 help_variable | $(TOPDIR)/.config/help variable +ifeq ($V,1) + $Q$(MAKE) -j1 V=0 help_example | $(TOPDIR)/.config/help example +endif + +help_target :: + @echo "help - print this help" + + +############################################### +# output customization # +############################################### + +help_variable :: + @echo "V - verbose output, if non-null" + +help_example :: + @echo "@@" + @echo "Verbose output" + @echo " make V=1" + @echo " make V=1 clean" + @echo " make V=1 tar" + +ifeq ($(MAKELEVEL),0) +export STARTDIR:=$(CURDIR) +endif + +# make V=1 - very verbose, prints all commands +# make V=0 - prints only titles [default] +ifeq ($V$(IS_HELP),1) +override Q := +else +override Q := @ +MAKEFLAGS += --no-print-directory +out := 2>/dev/null 1>/dev/null +endif +summary = @echo " $(1)" $(subst $(STARTDIR)/,,$(CURDIR)/)$(2) +summary2 = @printf " %-5s %s\n" "$(1)" "$(2)" +export V + + +############################################### +# build rules # +############################################### + +help_target :: + @echo "all - build all target" + @echo "install - install binaries" + +help_variable :: + @echo "DESTDIR - install under this dir rather " + @echo " then under /" + @echo "DEBUG - compile with debug symbols, if non-null" + +help_example :: + @echo "@@" + @echo "Installs stuff in a separate dir for easy packaging." + @echo " ./configure --prefix=/usr/local" + @echo " make" + @echo " make install DESTDIR=/tmp/tmp313231" + +help_example :: + @echo "@@" + @echo "All standard make variables, like CFLAGS, LDFLAGS or CC, " + @echo "are also supported." + @echo " make CFLAGS=-O2" + @echo " make CC=/opt/arm-gcc" + +ifeq ($(origin CFLAGS),environment) +ifeq ($(CFLAGS_orig),) +override CFLAGS_orig := $(CFLAGS) +export CFLAGS_orig +else +override CFLAGS := $(CFLAGS_orig) +endif +endif + +ifeq ($(origin CFLAGS),undefined) +CFLAGS = -O2 +endif +ifneq ($(origin DEBUG),undefined) +override CFLAGS += -g +endif +override CFLAGS += -I$(TOPDIR) $(CFLAGS_EXTRA) + +# Produce local obj name from C file path +src2base = $(basename $(notdir $(1))) + +# Create rule to compile an object from source +# Parameters +# $1 - C file (eg ../some/dir/test.c) +# $2 - object file basename (eg test) +# $3 - target name (eg hello) +define objng_rules +ifneq ($(IS_ALL),) +-include $(2).d +endif +$(3)_obj += $(2).o +CLEANLIST += $(2).o $(2).d +$(2).o : $(1) $(TOPDIR)/config.h + $(call summary,CC ,$$@) + $Q$(CC) $(CFLAGS) $($(3)_cflags) -c -o $$@ $$< -MMD $(out) + $Qsed -i -e 's/\s\/\S\+/ /g' $(2).d +endef + +define bin_rules +all : $(1) +CLEANLIST += $(1) + +$(foreach s,$($(1)_src),\ + $(eval $(call objng_rules,$(s),$(call src2base,$(s)),$(1)))) + +$(1) : $($(1)_obj) + $(call summary,BIN ,$$@) + $Q$(CC) $$^ $(LDFLAGS) $($(1)_libs) -o $$@ $(out) + +ifeq ($($(1)_install),) +install : $(1)_install +$(1)_install : + $Qinstall -D -m 755 -T $(1) $(DESTDIR)$(BINDIR)/$(1) +endif +endef + + +define lib_rules +all : lib$(1).so +CLEANLIST += lib$(1).so +$(eval $(1)_cflags += -fPIC) + +$(foreach s,$($(1)_src),\ + $(eval $(call objng_rules,$(s),$(call src2base,$(s)),$(1)))) + +lib$(1).so : $($(1)_obj) + $(call summary,LIB ,$$@) + $Q$(CC) $$^ $(LDFLAGS) $($(1)_libs) -shared -o $$@ $(out) + +ifeq ($($(1)_install),) +install : $(1)_install +$(1)_install : + $Qinstall -D -m 755 -T lib$(1).so $(DESTDIR)$(LIBDIR)/lib$(1).so +endif +endef + + +define ar_rules +all : lib$(1).a +CLEANLIST += lib$(1).a + + +$(foreach s,$($(1)_src),\ + $(eval $(call objng_rules,$(s),$(call src2base,$(s)),$(1)))) + +lib$(1).a : $($(1)_obj) + $(call summary,AR ,$$@) + $Q$(AR) rcs $$@ $$^ + +ifeq ($($(1)_install),) +install : $(1)_install +$(1)_install : + $Qinstall -D -m 644 -T lib$(1).a $(DESTDIR)$(LIBDIR)/lib$(1).a + +endif +endef + +ifeq ($(KVERSION),) +KVERSION := $(shell uname -r) +#export KVERSION +endif + +define kmod_rules +CLEANLIST += *.ko *.o *.mod.c .*.cmd .tmp_versions modules.order Module.symvers +all: + $(call summary,KMOD ,$(1).ko) + $Qexport KBUILD_EXTRA_SYMBOLS=$(KB_SYMBOLS); \ + make -C /lib/modules/$$(KVERSION)/build M=$(CURDIR) \ + modules $(out) +# clean: +# $Qmake -C /lib/modules/$$(KVERSION)/build M=$(CURDIR) \ +# clean $(out) + +install: + $Qmake -C /lib/modules/$$(KVERSION)/build M=$(CURDIR) \ + modules_install $(out) +endef + +% : %.in + @echo "TEXT $@" + $Q$(TOPDIR)/repl.py < $^ > $@ + + +targets = $(filter %_type,$(.VARIABLES)) +$(foreach t,$(targets),$(eval $(call $(strip $($(t)))_rules,$(t:_type=)))) + + +############################################### +# clean # +############################################### + +help_target :: + @echo "clean - clean build results" + @echo "distclean - clean build and configure results" + +ifeq ($(IS_TOPDIR),yes) +DISTCLEANLIST += config.mk config.h repl.py .config/*.pyc +endif + +clean: +ifneq (,$(CLEANLIST)) + $(call summary,CLEAN ,) + $Qrm -rf $(CLEANLIST) +endif + +distclean : clean +ifneq (,$(DISTCLEANLIST)) + $(call summary,DCLEAN ,) + $Qrm -rf $(DISTCLEANLIST) +endif + + +############################################### +# tar # +############################################### + +ifeq ($(IS_TOPDIR),yes) +help_target :: + @echo "tar - make tar archive of a project code" + +tar : + $(call summary,TAR ,/tmp/$(NV).tar.bz2) + $Q$(TOPDIR)/.config/tar.py $(if $V,-v) /tmp/$(NV).tar.bz2 +endif + + +############################################### +# misc # +############################################### + +help_target :: + @echo "svnignore - tell svn to ignore files in a cleanlist" + +svnignore: + @prop=prop-$$$$.txt; \ + for i in $(DISTCLEANLIST) $(CLEANLIST); do echo "$$i"; done > $$prop; \ + cat $$prop; \ + svn propset svn:ignore --file $$prop .; \ + rm -f $$prop + diff --git a/.config/tar.py b/.config/tar.py new file mode 100755 index 0000000..f76c9ee --- /dev/null +++ b/.config/tar.py @@ -0,0 +1,178 @@ +#!/usr/bin/python + +import subprocess as sp +import re, tempfile +import shutil, os, sys + +################################################## +# Loging +import logging +def _init_log(): + name = 'app' + x = logging.getLogger(name) + x.setLevel(logging.WARNING) + # x.setLevel(logging.DEBUG) + h = logging.StreamHandler() + # f = logging.Formatter("%(name)s (%(funcName)s:%(lineno)d) :: %(message)s") + f = logging.Formatter("%(message)s") + h.setFormatter(f) + x.addHandler(h) + return x + +x = _init_log() + +################################################## +# Argument parsing +import argparse +p = argparse.ArgumentParser( + description='Pack project source to tar archive.', + epilog = 'Example: \n%(prog)s /tmp/proj-1.2.tar.bz2' +) + +p.add_argument('-v', dest = 'verbose', action = 'store_true', + default = False, + help = 'Print more messages [%(default)s]') +p.add_argument('-d', '--debug', dest = 'debug', action = 'store_true', + default = False, + help = 'Print debug messages [%(default)s]') +p.add_argument('tar', nargs=1, + help = 'tar file to create') + +args = p.parse_args() +if args.verbose: + x.setLevel(logging.INFO) +if args.debug: + x.setLevel(logging.DEBUG) + +################################################## +# main +def my_check_output(*popenargs, **kwargs): + if 'stdout' in kwargs: + raise ValueError('stdout argument not allowed, it will be overridden.') + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + x.debug("exec: %s", cmd) + process = sp.Popen(stdout=sp.PIPE, *popenargs, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + raise sp.CalledProcessError(retcode, cmd, output=output) + return output + +def svn_is_used(): + f = open('/dev/null', 'w') + p = sp.Popen('svn info'.split(), stdout = f, stderr = f) + p.wait() + return p.returncode == 0 + + +def svn_get_file_list(): + text = my_check_output('svn info -R'.split()) + files = [] + tre = '(?m)^Path: (?P.*)$(.|\s)*?^Node Kind: (?P.*)$' + for m in re.finditer(tre, text): + if m.group('type') == 'file': + files.append(m.group('path')) + return files + +def git_is_used(): + f = open('/dev/null', 'w') + p = sp.Popen('git status'.split(), stdout = f, stderr = f) + p.wait() + return p.returncode == 0 + + +def git_get_file_list(): + text = my_check_output('git ls-tree --name-only -r HEAD'.split()) + files = text.split('\n') + return files + +def none_is_used(): + f = open('/dev/null', 'w') + p = sp.Popen('make -n distclean'.split(), stdout = f, stderr = f) + p.wait() + return p.returncode == 0 + +def none_get_file_list(): + f = open('/dev/null', 'w') + p = sp.Popen('make distclean'.split(), stdout = f, stderr = f) + p.wait() + if p.returncode: + return [] + files = [] + for (dirpath, dirnames, filenames) in os.walk('.'): + if dirpath.startswith('./'): + dirpath = dirpath[2:] + elif dirpath == '.': + dirpath = '' + filenames = [ os.path.join(dirpath, f) for f in filenames ] + files.extend(filenames) + return files + +vcs = [ + { + 'name' : 'svn', + 'is_used' : svn_is_used, + 'get_file_list' : svn_get_file_list + }, + { + 'name' : 'git', + 'is_used' : git_is_used, + 'get_file_list' : git_get_file_list + }, + + # must be last entry + { + 'name' : 'none', + 'is_used' : none_is_used, + 'get_file_list' : none_get_file_list + } +] + +# Get lists of files. If VCS is used, then lists only controlled files +# otherwise lists all files +def get_file_list(): + files = [] + for v in vcs: + ret = v['is_used']() + x.debug('trying %s, ret %d', v['name'], ret) + if ret: + x.info('VCS: %s', v['name']) + files = v['get_file_list']() + # remove duplicates, if any + files = list(set(files)) + files.remove('') + files.sort() + x.debug('File list: %s', files) + if not files: + x.error('No files. Aborting') + exit(2) + return files + x.error('No files. Aborting') + exit(2) + +tar = args.tar[0] +path, name = os.path.split(tar) +nv = re.sub('.tar.*', '', name) +x.debug('tar %s, nv %s', tar, nv) +# XXX: get compresion type from extension + +files = [ nv + '/' + f for f in get_file_list() ] +files = '\n'.join(files) +x.debug('%s', files) +tdir = tempfile.mkdtemp() +odir = os.path.abspath('.') +os.chdir(tdir) +os.symlink(odir, nv) +open('filelist', 'w').write(files) +cmd = 'tar hjcf %s --files-from filelist' % (tar,) +x.debug('exec: %s', cmd) +p = sp.Popen(cmd.split()) +p.wait() +if p.returncode: + x.error('Aborting') +else: + x.info('Tar: %s', tar) + +shutil.rmtree(tdir) diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..c63a738 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,455 @@ +6.2 +* 3367953: 'move to desktop' item in taskbar menu + +6.1 +New Features: +* 2977832: meter plugin - base plugin for icons slide show +* 2977833: battery plugin +* 2981332: volume plugin +* 2981313: Enhancements to 'tclock' plugin - calendar and transparency +* multiline taskbar: new config MinTaskHeight was added to set minimal + task/row height +* multiline launchbar: row height is MaxIconSize +* scrolling on panel changes desktops +* dclock vertical layout was implemented. It still draws digits + horizontaly if there is enough space +* new global config MaxEelemHeight was added to limit plugin elements + (eg icons, messages) height +* 993836: add GTK frame to non-transparent chart plugins + +Fixed Bugs: +* 2990621: add charging icons to battery plugin +* 2993878: set menu icons size from panel config not from gtk rc +* fixed locale/NLS issues with configure +* chart class struct was made static +* 2979388: configure broken - problems in busybox environments +* fixing variable name check in configure +* 2985792: Menu disappears too quickly +* 2990610: panel with autohide disappears when positioned at (0,0) +* 2990620: fix dclock for vertical orientation +* 2991081: do not autohide panel when menu is open +* 3002021: reduce sensitive area of hidden panel + +6.0 +* adding xlogout script +* fixing cpu and net plugins to recover after /proc read errors +* menu: new code to build system menu +* GUI configurator code was reworked +* common API to run external programs was added +* new configuration system - xconf - was introduced +* adding png icons for reboot and shutdown. They may be missing in + some icon themes. +* all svg icons were removed +* automatic profile created +* show calendar as default action in dclock +* fixed 'toggle iconfig all' algorithm +* 2863566: Allow seconds in dclock plugin +* 2972256: rebuild code upon makefile changes +* 2965428: fbpanel dissapears when configuring via GUI +* 2958238: 5.6 has bugs in configure script and fails to load plugins +* 2953357: Crashes when staring Opera + +5.8 +* moving config dir ~/.config +* automatic new profile creation +* removing app categories default icons +* dclock plugin pops up calendar if no action was set +* net plugin got detailed tooltip and color configs +* cpu plugin got detailed tooltip and color configs +* mem plugin was made +* allocating plugin's private section as part of instance malloc +* drag and drop fix in launchbar +* Fixed "2891558: widthtype=request does not work" + + +5.7 +* XRandR support (dynamic desktop geometry changes) +* Fixed "2891558: widthtype=request does not work" +* configurator redraws panel on global changes +* fixing 'toggle iconify all' algorithm + + +5.6 +* genmon plugin - displays command output in a panel +* CFLAGS propagation fix + +5.5 +* adding static build option for debugin purposes e.g to use with valgrind +* ability to set CFLAGS from command line was added. + make CFLAGS=bla-bla works correctly +* fixing memory leaks in taskbar, menu and icons plugin + +5.4 +* fb_image and icon loading code refactoring +* chart: making frame around a chart more distinguishable +* taskbar: enable tooltips in IconsOnly mode +* taskbar: build tooltips from text rather then from markup + + +5.3 +* when no icon exists in a theme, missing-image icon is substituted. theme-avare +* prevent duplicate entries in menu +* menu plugin uses simple icons, and rebuild entire menu upon theme change, + rather then creating many heavy theme-aware icons, and let them update +* cpu, net plugins: linux specific code was put into ifdefs and stub for another + case wwas created +* system menu icon was renamed to logo.png from star.png +* strip moved to separete target and not done automatically after compile +* by default make leaves output as is, to see summaries only run 'make Q=1' +* enbling dependency checking by default +* adding svn ebuild fbpanel-2009.ebuild +* adding tooltips to cpu and net plugins +* BgBox use BG_STYLE by default +* close_profile function was added to group relevant stuff +* autohide was simplified. Now it hides completly and ignoress heightWhenHidden + + + +5.2 +* fixing segfault in menu plugin +* extra spaces in lunchbar plugin were removed +* replacing obsolete GtkTooltips with GtkTooltip +* plugins' install path is set to LIBDIR/fbpanel instead of LIBEXECDIR/fbpanel +* fixing short flash of wrong background on startup + + + + +5.1 +* Tooltips can have mark-uped text, like 'Terminal' +* Cpu plugin is fixed and working +* Added general chart plugin (used by cpu and net monitors) +* Code layout was changed, new configure system and new makefiles set was + adopted +* fixed segfault in taskbar plugin +* background pixmap drawing speed ups and bugfixes + +4.13 +New Features: +* support for "above all" and "below all" layering states. Global section + was added string variable + Layer = None | Above | Below +* to speed start-up, panel does not have window icon, only configuator window has +* Control-Button3 click launches configureation dialog +* taskbar was changed to propagate Control-Button3 clicks to parent window i.e to panel +* launchbar was changed to propagate Control-Button3 clicks to parent window +* pager was changed to propagate Control-Button3 clicks to parent window +* dclock was changed to propagate Control-Button3 clicks to parent window +* menu was changed to propagate Control-Button3 clicks to parent window +* normal support for round corners. Config file gets new global integer option - RoundCornersRadius +* system tray transparency fix +* clock startup delay was removed +* menu: fixed segfault caused by timeout func that used stale pointer + + + +4.12 +New Features: +* smooth icon theme change without panel reload +* autohide. Config section is part of 'Global' section + autoHide = false + heightWhenHidden = 2 +* 3 sec delayed menu creation to improve start-up time + +Fixed Bugs: +* icons, taskbar do not free all tasks when destroyed + + +4.11 +Fixed Bugs: +* black background when no bg pixmap and transparency is unset + +4.10 +New Fetures: +* tclock: dclock was renamed to tclock = text clock +* dclock: digital blue clock. adopted from blueclock by + Jochen Baier +* dclock: custom clock color can be set with 'color' option + Plugin { + type = dclock + config { + TooltipFmt = %A %x + Action = xterm & + color = wheat + } + } + +* menu: items are sorted by name +* menu: icon size set to 22 +* launchbar: drag-n-drop now accepts urls draged from web browsers +* style changes are grouped and only last of them is processed + +Fixed Bugs: +* menu: forgoten g_free's were added +* 1723786: linkage problems with --as-needed flag +* 1724852: crash if root bg is not set +* WM_STATE usage is dropped. NET_WM_STATE is used instead. affected plugins are + taskbar and pager +* fixed bug where pager used unupdated panel->desknum instead of pager->desknum +* all Cardinal vars were changed to guint from int +* bug in Makefile.common that generated wrong names in *.dep files +* style changes are grouped and only last of them is processed + +4.9 +* new menu placement to not cover panel; used in menu and taskbar +* taskbar: icons were added to task's menu (raise, iconify, close) +* access to WM_HINTS is done via XGetWMHints only and not via get_xa_property; + in taskbar it fixes failure to see existing icon pixmap +* 1704709: config checks for installed devel packages + +4.8 +* help text in configurator was made selectable +* pager shows desktop wallpaper +* expanding tilda (~) in action field in config files +* menu icons size was set to 24 from 22 to avoid scaling +* avoid re-moving panel to same position +* plugins section in configurator dialog suggests to edit config manually +* taskbar vertical layout was fixed +* taskbar 'icons only' mode was optimized +* fbpanel config window has nice "star" icon + +4.7 +New Feature +* Build application menu from *.desktop files +* Using themed icons. Change icon theme and see fbpanel updates itself +* default config files were updates to use new functionality + +4.6 +New Features +* [ 1295234 ] Detect Window "Urgency". +* Raise window when drag target is over its name in taskbar +* fixing meory leaks from XGetWindowProperty. +* fix urgency code to catch up urgency of new windows +* taskbar: correct position of task's label +* taskbar: remove extra spaces +* taskbar: do not create event box beneath task button +* taskbar: use default expose method in gtk_bar +* taskbar; use default expose method in task button +* taskbar: cleaning up dnd code +* launchbar: visual feedback on button press + +4.5 +Fixed bugs +* Makefile.common overwrite/ignore CFLAGS and LDFLAGS env. variables +* rebuild dependancy Makefiles (*.dep) if their prerequisits were changed +* fixing gcc-4.1 compile warnings about signess +* removing tar from make's recursive goals +* fixing NET_WM_STRUT code to work on 64 bit platforms + +New features +* porting plugins/taskbar to 64 bit +* porting plugins/icons to 64 bit +* adding LDFLAGS=-Wl,-O1 to Makefile +* adding deskno2 plugin; it shows current desktop name and allow to scroll over available desktops +* applying patch [ 1062173 ] NET_ACTIVE_WINDOW support +* hiding tray when there are no tray icons +* remove extra space around tray +* using new icons from etiquette theme. droping old ones + +4.4 +New Feature +* 64-bit awarenes + +4.3 +New Feature +* [1208377] raise and iconify windows with mouse wheel +* [1210550] makefile option to compile plugins statically +* makefile help was added. run 'make help' to get it +* deskno gui changes + +Fixed Bugs +* deskno can't be staticaly compiled +* typo fixes +* Makefile errors for shared and static plugin build + +4.2 +Fixed Bugs +* [1161921] menu image is too small +* [1106944] ERROR used before int declaration breaks build +* [1106946] -isystem needs space? +* [1206383] makefile fails if CFLAGS set on command line +* [1206385] DnD in launchbar fails if url has a space +* fixed typos in error messages + +New Feature +* New code for panel's buttons. Affected plugins are wincmd, launchbar and menu +* Depreceted option menu widget was replaced by combo box +* sys tray is packed into shadowed in frame +* pad is inserted betwean tasks in a taskbar +* clock was made flat + +4.1 +New Feature +* gui configuration utility +* transparency bug fixes + +4.0 +New Feature +* plugins get root events via panel's proxy rather then directly +* added configure option to disable cpu plugin compilation + +3.18 +New Feature +* [ 1071997 ] deskno - plugin that displays current workspace number +Fixed Bugs +* [ 1067515 ] Fixed bug with cpu monitor plugin + + +3.17 +Fixed Bugs +* [ 1063620 ] 3.16 crashes with gaim 1.0.2 sys tray applet +New Feature +* [ 1062524 ] CPU usage monitor + + +3.16 +New Feature +* taskbar does not change window icons anymore. +* invisible (no-gui) plugin type was introduced +* icons plugin was implemented. it is invisible plugin used to changes + window icons with desktop-wide effect. + + +3.15 +Fixed Bugs +* [ 1061036 ] segfault if tray restarted + +3.14 +New Feature +* [ 1010699 ] A space-filler plugin +* [ 1057046 ] transparency support +* all static plugins were converted to dlls +* added -verbose command line option +Fixed Bugs +* dynamic module load fix + +3.13 +New Feature +* [ 953451 ] Add include functionality for menu config file. +Fixed Bugs +* [ 1055257 ] crash with nautilus+openbox + +3.12 +New Features +* [ 976592 ] Right-click Context menu for the taskbar + +3.11 +* fixed [ 940441 ] pager loose track of windows + +3.10 +* fix for "996174: dclock's 'WARNING **: Invalid UTF8 string'" +* config file fix + +3.9 +* fix bg change in non transparent mode +* enable icon only in taskbar +* ensure all-desktop presence if starting before wm (eg openbox) +* wincmd segfault fix + +3.8 +* warnings clean-up +* X11 memory leacher was fixed +* taskbar can be set to show only mapped/iconified and wins from other desktops +* transparency initial support +* gtkbar was ported to gtk2, so fbpanel is compiled with GTK_DISABLE_DEPRECETED +* initial dll support + +3.7 +* rounded corners (optional) +* taskbar view fix + +3.6 +* taskbar icon size fix +* menu icon size fix +* pager checks for drawable pixmap + +3.5 +* Drag-n-Drop for launchbar +* menu plugin +* removed limith for max task size in taskbar + +3.4 +* gtk2.2 linkage fix +* strut fix +* launchbar segfault on wrong config fix +* '&' at the end of action var in launchbar config is depreciated + +3.3 +* taskbar icon size fix + +3.2 +* scroll mouse in pager changes desktops +* packaging and makefiles now are ready for system wide install + additionally ./configure was implemented +* systray checks for another tray already running + +3.1 +* improving icon quility in taskbar +* system tray (aka notification area) support +* NET_WM_STRUT_PARTIAL and NET_WM_STRUT were implmented +* taskbar update icon image on every icon change + +3.0 +* official version bump :-) + +3.0-rc-1 +* porting to GTK2+. port is based on phako's patch + "[ 678749 ] make it compile and work with gtk2" + + +2.2 +* support for XEmbed docklets via gtktray utility + +2.1 +* tray plugin was written +* documentation update +* web site update + +2.0 +* complete engine rewrite +* new plugin API +* pager fixes + +1.4 +* bug-fixes for pager plugin + +1.3 +* middle-click in taskbar will toggle shaded state of a window +* added image plugin - this is simple plugin that just shows an image +* pager eye-candy fixes +* close_module function update + +1.2 +* we've got new module - pager! Yeeaa-Haa!! +* segfault on wrong config file was fixed + +1.1 +* parsing engine was rewritten +* modules' static variables were converted to mallocs +* configurable size and postion of a panel +* ability to specify what modules to load +* '~' is accepted in config files +* + + +1.0 +* 1.0-rc2 was released as 1.0 + +1.0-rc2 +* taskbar config file was added an option to switch tooltips on/off +* added tooltips to taskbar (thanks to Joe MacDonald joe@deserted.net) + +1.0-rc1 +* copyright comments were changed + +1.0-rc0 +* added _NET_WM_STRUT support +* panel now is unfocusable. this fixes iconify bug under sawfish +* panel's height is calculated at run-time, instead of fixed 22 + +0.11 +* improved EWMH/NETWM support +* added openbox support +* added clock customization (thanks to Tooar tooar@gmx.net) +* README was rewrited +* bug fixes diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..bc80143 --- /dev/null +++ b/COPYING @@ -0,0 +1,21 @@ +Copyright (C) 2002 Anatoly Asviyan (aka Arsen) +Copyright (C) 2000 Peter Zelezny + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Soft- +ware"), to deal in the Software without restriction, including without +limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to +whom the Software is furnished to do so, subject to the following condi- +tions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- +ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT +OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/CREDITS b/CREDITS new file mode 100644 index 0000000..bccdeea --- /dev/null +++ b/CREDITS @@ -0,0 +1,21 @@ +I'd like to thank GNOME project. I learn a lot from its code +and some pieces were used in fbpanel. Correct for version 3.0, +systray code and wm icon code were copied from GNOME + +Credits to all people who contributed by writing code and solving +bugs. + PCMan + Urgency hint implementation + Raise window when drag target is over its name in taskbar + Building menu from *.desktop files + Joe MacDonald + Jens Georg + and others + +Credits and lot of thanks to Peter Zelezny +an author of fspanel (see http://www.chatjunkies.org/fspanel/ + or http://freshmeat.net/projects/fspanel/) +The first version of fBpanel was started as hacking around fSpanel :-) + +Nice battery icons for battery meter plugin were taken +from http://rocketdock.com/addon/docklets/9523 diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..8e73413 --- /dev/null +++ b/INSTALL @@ -0,0 +1,17 @@ +Installation: + +1. Default way +Most users (99.99%) should use this way :-) + + ./configure + make + su - + make install + +2. Litle customization +Run ./configure --help to see supported options, then goto step 1 + + + + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..78fd88c --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := . + +SUBDIRS := data \ + exec \ + panel \ + plugins \ + po \ + scripts + +include $(TOPDIR)/.config/rules.mk diff --git a/NOTES b/NOTES new file mode 100644 index 0000000..184965c --- /dev/null +++ b/NOTES @@ -0,0 +1,21 @@ +6.2 + +6.1 +This release features major enhancements and bug fixes, namely: +* new plugin - laptop battery monitor +* new plugin - volume control +* multiline placement of elements in taskbar and launchbar +* digital clock in vertical layouts draws hours above minutes +* fixed compilation issues in busybox environments +* fixed locale/NLS issues in configure +* autohide was improved + +6.0 +This release features minor enhancements and bug fixes, namely: +* redesign of configuration engine to be faster and simpler +* script to logout from user session +* ability to show seconds in dclock plugin +* bug fixes in mem, taskbar and menu plugins +* usage of current icon theme was ensured and private icons were + removed from package + diff --git a/README b/README new file mode 100644 index 0000000..2d884bb --- /dev/null +++ b/README @@ -0,0 +1,64 @@ + fbpanel + +NAME + fbpanel is a lightweight GTK2-based panel for UNIX desktop + +SYNOPSYS + fbpanel [OPTION]... + +DESCRIPTION + fbpanel is desktop panel which provides graphical information and feedback about + desktop activity and allows interaction with the window manager. + It features: + o taskbar - show list of the managed windows (tasks) + o pager - thumbnailed view of the desktop + o launchbar - buttons to quickly launch applications + o show desktop - button to iconify or shade all windows + o image - display an image + o clock - show the current time and/or date + o system tray - tray for XEMBED icons (aka docklets) + o space - just seize space + o menu - menu with text and images + o deskno - display desktop number and name + fbpanel requires NETWM (www.freedesktop.org) compliant window manager. + + You can run many instances of fbpanel each with its own configuration + (see OPTIONS below). + + Most updated info about fbpanel can be found on its home page: + http://fbpanel.sf.net/ + +OPTIONS + --help -- print this help and exit + --version -- print version and exit + --log -- set log level 0-5. 0 - none 5 - chatty + --configure -- launch configuration utility + --profile name -- use specified profile + + -h -- same as --help + -p -- same as --profile + -v -- same as --version + -C -- same as --configure + + +CUSTOMIZATION + To change default settings, copy profile file to your home directory + mkdir -p ~/.fbpanel + cp /usr/share/fbpanel/default ~/.fbpanel + and edit it. Default profile file contains comments and explanation inside, + so it should be easy. For full list of options please visit fbpanel's home page + +FILES + /usr/share/fbpanel + Directory with system wide resources and default settings + + ~/.fbpanel/ + Directory with user's private configurations + + ~/.fbpanel/default + Default profile + +AUTHORS + Anatoly Asviyan + + diff --git a/configure b/configure new file mode 100755 index 0000000..c5e3f3a --- /dev/null +++ b/configure @@ -0,0 +1,502 @@ +#!/usr/bin/python + +import sys +if sys.version_info < (2, 7): + sys.path.insert(0, '.config') + +########################################################## +### Arg Parser ### +########################################################## + +import argparse, textwrap + +class ToggleAction(argparse.Action): + def __init__(self, **kw): + name = kw['option_strings'][0][2:] + if name.startswith('no-'): + name = name[3:] + kw['dest'] = name + kw['option_strings'] = [ '--no-' + name, '--' + name ] + kw['nargs'] = 0 + kw['metavar'] = None + super(ToggleAction, self).__init__(**kw) + + def __call__(self, parser, namespace, values, option_string=None): + if option_string and option_string.startswith('--no-'): + setattr(namespace, self.dest, False) + else: + setattr(namespace, self.dest, True) + + +class SmartHelpFmt(argparse.RawDescriptionHelpFormatter): + def _format_action(self, action): + if type(action) == ToggleAction: + opts = action.option_strings + action.option_strings = [ '--[no-]' + action.dest ] + parts = super(SmartHelpFmt, self)._format_action(action) + action.option_strings = opts + else: + parts = super(SmartHelpFmt, self)._format_action(action) + return parts + + def _format_usage(self, usage, actions, groups, prefix): + for g in groups: + print(g) + text = super(SmartHelpFmt, self)._format_usage(usage, + actions, groups, prefix) + # print "Usage", text + return text + + def format_help(self): + text = super(SmartHelpFmt, self).format_help() + return text + + +# Create help group with general '--help' option and with +# specific '--help-GROUP' for every argument group +class ArgParse(argparse.ArgumentParser): + def __init__(self, **kw): + self.help_names = [] + self.help_groups = {} + self.default_group = None + kw['add_help'] = False + kw['formatter_class'] = SmartHelpFmt + super(ArgParse, self).__init__(**kw) + self.add_argument_group('help', 'Help options', None) + self.add_argument("-h", "--help", help = "Print help and exit", + action = 'store_true', group = 'help') + + def add_argument_group(self, name, title=None, description=None, + default = None): + # print "add_argument_group: '%s'" % name, title, description, default + if name in self.help_groups: + raise BaseException("help group %s already exists" % name) + self.help_groups[name] = super(ArgParse, + self).add_argument_group(title, description) + if name != 'help' and len(name.split()) == 1: + self.add_argument("--help-" + name, group = 'help', + action = 'store_true', + help = "Help on " + title) + if default: + self.default_group = self.help_groups[name] + return self.help_groups[name] + + def add_argument(self, *args, **kw): + # print "add_argument:", args, kw + if 'group' in kw: + group = self.help_groups[kw['group']] + del kw['group'] + return group.add_argument(*args, **kw) + elif self.default_group: + return self.default_group.add_argument(*args, **kw) + else: + return self.add_argument(*args, **kw) + + + def format_help(self): + all = 'help' in self.help_names + if not all: + self.help_names = [ n[5:] for n in self.help_names] + + formatter = self._get_formatter() + + if all: + u = "%(prog)s " + for g in self._action_groups: + if g.title and g._group_actions: + u += '[_%s_] ' % g.title.replace(' ', '_') + u = textwrap.fill(u, + initial_indent = ' ' * 15, + subsequent_indent = ' ' * 13, + break_long_words = False).strip().replace('_', ' ') + + formatter.add_usage(u, None, []) + formatter.add_text(self.description) + + if all: + self.help_names = self.help_groups.keys() + + for name in self.help_names: + group = self.help_groups[name] + formatter.start_section(group.title) + formatter.add_text(group.description) + formatter.add_arguments(group._group_actions) + formatter.end_section() + + if all: + formatter.add_text(self.epilog) + + return formatter.format_help() + + def parse_args(self, args=None, namespace=None): + a = super(ArgParse, self).parse_args(args, namespace) + self.help_names = [ n for n in dir(a) if + (n == 'help' or n.startswith("help_")) and getattr(a, n) ] + if self.help_names: + self.print_help() + exit(0) + + return a + + +########################################################## +### log ### +########################################################## + +import subprocess as sp, re, sys, os, datetime +import traceback, tempfile, shutil +import logging +x = logging.getLogger('app') +def init_log(debug, verbose, *args): + for name in args: + x = logging.getLogger(name) + if debug: + f = logging.Formatter("%(name)s (%(funcName)s:%(lineno)d) " + + ":: %(message)s") + verbose = True + else: + f = logging.Formatter("%(message)s") + + h = logging.StreamHandler() + h.setFormatter(f) + x.addHandler(h) + if verbose: + x.setLevel(logging.DEBUG) + else: + x.setLevel(logging.INFO) + +p = ArgParse() + + +########################################################## +### Varables ### +########################################################## + +vars = {} + +def assign_cmd_vars(): + for v in args.vars: + x.debug("test %s", v) + m = re.match('^[A-Z_]+=.*', v) + if not m: + continue + l = v.split('=') + name = l[0] + value = l[1] + vars[name] = value + x.debug("name %s, value '%s'", name, value) + + +########################################################## +### Options ### +########################################################## +def my_check_output(*popenargs, **kwargs): + if 'stdout' in kwargs: + raise ValueError('stdout argument not allowed, it will be overridden.') + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + x.debug("exec: %s", cmd) + process = sp.Popen(stdout=sp.PIPE, *popenargs, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + output = output.decode("utf-8").strip() + if retcode: + raise sp.CalledProcessError(retcode, cmd, output=output) + return output + + +opts = {} +opt_order = [] +args = None +help_groups = {} +stack = [] +def opt(name): + global stack, opts, help_groups + + if name in stack: + raise BaseException("dep loop: second '" + name + "' in " + + str(stack + [name])) + stack.append(name) + x = opts[name] + if callable(x): + while callable(x): + x = x() + opts[name] = x + del stack[-1] + return x + + +def opt_group_new(name, desc, long_desc = None, default = None): + global help_groups + + if name in help_groups: + raise BaseException("Help group %s' already exists" % name) + help_groups[name] = p.add_argument_group(name, desc, long_desc, default) + +def opt_new(name, **kw): + global opts, opt_order + + if not re.match('^[a-zA-Z_][0-9a-zA-Z_]*$', name): + raise BaseException("Illegal opt name '%s'" % name) + if name in opts: + raise BaseException("opt %s' already exists" % name) + if 'help' in kw: + if not 'group' in kw: + help_groups['app'].add_argument("--" + name, **kw) + else: + group = kw['group'] + del kw['group'] + help_groups[group].add_argument("--" + name, **kw) + d = None + if 'default' in kw: + d = kw['default'] + if type(d) == str: + d = d.strip() + opts[name] = d + opt_order.append(name) + +# Creates set of variables from pkg-config output. +# First, it creates var for cflags and libs +# vname_cflags :: pkg-config pname --cflags +# vname_libs :: pkg-config pname --libs +# Then, for every var in vars +# vname_var :: pkg-config pname --variable=var +# +# Parameters: +# vname - prefix for variable names +# pname - pkg-config package name +# pvars - list of package variables, eg ['prefix', 'datadir'] +# pversion - version condition, eg '--atleast-version=2.14' +# required - true, for mandatory packages +def opt_new_from_pkg(vname, pname, pvars = [], pversion = "", required = True): + cmd = ['pkg-config', '--print-errors', pname] + try: + my_check_output(cmd + pversion.split(), stderr=sp.STDOUT) + except sp.CalledProcessError as e: + if required: + print(e.output) + print("This usually means that '" + pname + \ + "' development files are not installed") + exit(1) + else: + return + + opt_new(vname + '_libs', + default = my_check_output(cmd + ['--libs'])) + opt_new(vname + '_cflags', + default = my_check_output(cmd + ['--cflags'])) + opt_new(vname + '_version', + default = my_check_output(cmd + ['--modversion'])) + for v in pvars: + opt_new(vname + '_' + v, + default = my_check_output(cmd + ['--variable=' + v])) + +def pkg_exists(pname, extra = ""): + cmd = ['pkg-config', pname] + extra.split() + return sp.call(cmd) == 0 + + +def opt_set(name, value): + global opts + x.debug("set %s = '%s'", name, value) + opts[name] = value + +def assign_cmd_opts(): + global opts + x.debug("assign") + for k in [ y for y in dir(args) if y[:1] != '_' and y != 'vars' ]: + opts[k] = getattr(args, k) + x.debug("opts[%s] = '%s'", k, opts[k]) + +def write_config(): + now = datetime.datetime.now() + smake = "# Generated at %s \n# by command %s\n\n" % (now, sys.argv) + sc = "// Generated at %s \n// by command %s\n\n" % (now, sys.argv) + + for name in sorted(vars.keys()): + smake += "%s ?= %s\n" % (name, vars[name]) + smake += "\n" + + for name in opt_order: + v = opt(name) + # print name, type(v) + if not v: + continue + if type(v) == str: + v = v.replace('\n', '\\\n') + v = v.replace('"', '') + elif type(v) == bool: + v = int(v) + + smake += "%s := %s\n" % (name.upper(), str(v)) + if type(v) == str or type(v) == unicode: + v = '"' + v + '"' + sc += '#define %s %s\n' % (name.upper(), str(v)) + smake += "\n" + sc += "\n" + print('Created config.mk') + open('config.mk', 'w').write(smake) + print('Created config.h') + open('config.h', 'w').write(sc) + + +def write_repl(): + s = '' + for name in opt_order: + s += " '%s' : '%s',\n" % (name, str(opt(name))) + s = re.sub('#repl_dict#', s, open('.config/repl.py', 'r').read()) + open('repl.py', 'w').write(s) + os.chmod('repl.py', 0o755) + + +def init(): + pass + +def resolve(): + pass + +def report(): + pass + +########################################################## +### Self Install ### +########################################################## + +def mc_update(): + x.info("Downloading latest code") + + dtmp = tempfile.mkdtemp() + repo = 'https://github.com/aanatoly/miniconf.git' + + cmd = 'git clone -q %s %s' % (repo, dtmp) + my_check_output(cmd, shell = True) + + cmd = 'rsync -a %s/engine/ .' % dtmp + my_check_output(cmd, shell = True) + + shutil.rmtree(dtmp) + +def mc_makefiles(): + header_text = '## miniconf makefiles ## %d.%d ##\n' + header_re = '(?m)^## miniconf makefiles ## (\d+)\.(\d+) ##\n' + def makefile_needed(dirname): + x.debug("makefile_needed '%s'", dirname) + if dirname.split('/')[-1] == '.config': + x.debug("no - this dir is ignored") + return False + try: + h = open(os.path.join(dirname, 'Makefile'), 'r').readline() + except BaseException as e: + x.debug("yes - no read - %s", e) + return True + m = re.match(header_re, h) + if not m: + x.debug("yes - no header - %s", h) + return True + x.debug("no - header is ok") + return False + + x.info("Creating makefiles") + for dirname, dirs, filenames in os.walk('./'): + # remove starting './' + dirname = dirname[2:] + x.debug("dirname %s", dirname) + if dirname == '': + try: + dirs.remove('.config') + except: + pass + + for d in ['.git', '.svn', 'CVS']: + try: + dirs.remove(d) + except: + pass + x.debug("dirs: %s", dirs) + if not makefile_needed(dirname): + continue + level = len([ v for v in dirname.split('/') if v ]) + topdir = '/'.join(['..'] * level) + if not topdir: topdir = '.' + text = header_text % (1, 1) + text += '\nTOPDIR := ' + topdir + '\n\n' + if dirs: + text += 'SUBDIRS := ' + ' \\\n '.join(sorted(dirs)) + '\n\n' + + cfiles = sorted([ f for f in filenames if f.endswith('.c') ]) + cfiles = ' \\\n '.join(cfiles) + x.debug('cfiles %s', cfiles) + if cfiles: + name = os.path.basename(dirname) + text += '%s_src = %s\n' % (name, cfiles) + text += '%s_cflags = \n' % name + text += '%s_libs = \n' % name + text += '%s_type = \n' % name + text += '\n' + text += 'include $(TOPDIR)/.config/rules.mk\n' + + path = os.path.join(dirname, 'Makefile') + x.info("Create %s", path) + open(path, 'w').write(text) + + + print( "\nNow, you can test it. Run the commands:" ) + print( "./configure --help" ) + print( "./configure" ) + print( "make help" ) + print( "make V=1" ) + + +########################################################## +### main ### +########################################################## + +def main(): + global p, x, args + + opt_group_new('miniconf', 'miniconf installation options') + p.add_argument("--mc-update", group = 'miniconf', + help = "update or install configure scripts", + action = 'store_true') + p.add_argument("--mc-makefiles", group = 'miniconf', + help = "create Makefiles for a project", + action = 'store_true', default = False) + p.add_argument("--mc-debug", group = 'miniconf', + help = "enable debug output in miniconf", + action = 'store_true', default = False) + + opt_group_new('make', 'Makefile vars') + p.add_argument("vars", group = 'make', + help = "makefile vars, like 'CFLAGS=-02'", nargs='*') + + init() + args = p.parse_args() + init_log(args.mc_debug, 0, 'app') + mc = 0 + if args.mc_update: + mc_update() + mc = 1 + if args.mc_makefiles: + mc_makefiles() + mc = 1 + + if mc: + exit(0) + + assign_cmd_vars() + assign_cmd_opts() + resolve() + write_config() + write_repl() + report() + +if __name__ == "__main__": + try: + name = '.config/options.py' + code = compile(open(name, "r").read(), name, 'exec') + exec(code) + except: + x.info("Can't read .config/options.py") + pass + main() diff --git a/data/Makefile b/data/Makefile new file mode 100644 index 0000000..6538c05 --- /dev/null +++ b/data/Makefile @@ -0,0 +1,9 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := .. + +SUBDIRS := config \ + images \ + man + +include $(TOPDIR)/.config/rules.mk diff --git a/data/config/Makefile b/data/config/Makefile new file mode 100644 index 0000000..27ea677 --- /dev/null +++ b/data/config/Makefile @@ -0,0 +1,13 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +TEXT = default pager +all : $(TEXT) +CLEANLIST += $(TEXT) + +install : + $Qinstall -d $(DESTDIR)$(DATADIR) + $Qinstall -m 644 $(TEXT) $(DESTDIR)$(DATADIR) + +include $(TOPDIR)/.config/rules.mk diff --git a/data/config/default.in b/data/config/default.in new file mode 100644 index 0000000..4adbb27 --- /dev/null +++ b/data/config/default.in @@ -0,0 +1,398 @@ +######################################## +## fbpanel configuration file ## +######################################## + + +# DESCRIPTION +# Configuration file consists of mandatory 'Global' block that MUST come first, +# and optionally one or more 'Plugin' block. +# Lines having '#' as first non-blank char or blank lines are ignored +# Keywords are not case-sensitive +# Values are case-sensitive +# Value of variable is a text from first non-blank char after '=' +# till the last non-blank char. '#' is NOT treated as coment in this context + +# 'Global' block describes global parameters like position, size and +# some NETWM settings + +# Global { + +# # screen edge +# # legal values are: left, right, top, bottom +# edge = bottom + +# # allignment of a panel +# # legal values are: left, right, center +# allign = left + +# # length of margin (in pixels) +# # legal values are numbers +# margin = 0 + +# # widthtype specifies how panel width is calculated +# # legal values are: request, pixel, percent +# # request - follow widgets' size requests. can shrink or grow dynamically +# # pixel - occupy fixed number of pixels, then 'width' variable holds a number +# # percent - be 'width' precent of an edge. +# widthType = percent + +# # numerical value of width (not applicable for 'request' widthtype) +# # legal values are numbers +# width = 80 + +# # heighttype specifies how panel height is calculated +# # legal values are: pixel +# # pixel - ocupy fixed number of pixels, then 'height' variable holds a number +# heightType = pixel + +# # numerical value of height (if applicable) +# # legal values are numbers +# height = 28 + + +# # Identify panel window type as dock +# # legal values are boolean +# setDockType = true + +# # Reserve panel's space so that it will not be covered by maximazied windows +# # legal values are boolean +# # setPartialStrut = true + + +# # Transparency stuff: +# # tintColor is a color to composite on root background given as #RRGGBB or as name +# # alpha is transparency of the tint color. +# # transparent = true +# # tintColor = #FFFFFF +# or +# # tintColor = white +# # alpha = 127 + +# # Autohide +# # autoHide = false +# # heightWhenHidden = 2 + +# } + + + +# 'Plugin' block specifies a plugin to load. It has same syntax for both +# builtin and external plugins. + +# First parameter is 'type'. It's mandatory and must come first +# Legal values are plugin names. Names of builtin plugins are: +# separator - visual separator +# wincmd - 'show desktop' button +# taskbar - lists all opened windows (tasks) +# launchbar - bar with launch button +# image - just shows an image +# dclock - digital clock +# space - just seize space +# pager - thumbnailed view of the desktop +# tray - tray for XEMBED icons (aka docklets) + +# expand - specifies if plugin can accomodate extra space or not [optional] +# padding - extra padding around plugin [optional] +# config {} - block of plugin's private configuration. +# This part is plugin dependant + + +# +# Plugin { +# type = wincmd +# config { +# image = ~/.fbpanel/images/Desktop2.png +# tooltip = Left click to iconify all windows. Middle click to shade them. +# } +# } + + +Global { + edge = bottom + allign = center + margin = 0 + widthtype = percent + width = 86 + height = 24 + transparent = true + tintcolor = #ffffff + alpha = 28 + setdocktype = true + setpartialstrut = true + autohide = false + heightWhenHidden = 2 + roundcorners = true + roundcornersradius = 7 + layer = none + MaxElemHeight = 32 +} + + + +Plugin { + type = space + config { + size = 2 + } +} + + +Plugin { + type = menu + config { + IconSize = 22 + #icon = start-here + icon = logo + systemmenu { + } + separator { + } + menu { + name = Computer + icon = computer + + item { + name = Terminal + icon = terminal + action = x-terminal + } + item { + name = Lock Display + icon = gnome-lockscreen + action = slock + } + separator { + } + item { + name = Reboot + icon = gnome-session-reboot + action = sudo reboot + } + item { + name = Shutdown + icon = gnome-session-halt + action = sudo shutdown -h now + } + item { + name = logout + icon = gnome-session-logout + action = @libexecdir@/xlogout + } + } + } +} + + + +Plugin { + type = space + config { + size = 15 + } +} + + +Plugin { + type = launchbar + config { + button { + icon = file-manager + tooltip = File Manager + action = x-file-manager + } + button { + icon = terminal + tooltip = Terminal + action = x-terminal + } + button { + icon = web-browser + tooltip = Web Browser + action = x-www-browser + } + } +} + +Plugin { + type = space + config { + size = 15 + } +} + + +Plugin { + type = wincmd + config { + icon = gnome-fs-desktop + tooltip = Left click to iconify all windows. Middle click to shade them. + } +} + + +Plugin { + type = space + config { + size = 15 + } +} + + + +Plugin { + type = taskbar + expand = true + config { + ShowIconified = true + ShowMapped = true + ShowAllDesks = false + tooltips = true + IconsOnly = false + MaxTaskWidth = 150 + } +} + + +Plugin { + type = space + config { + size = 15 + } +} + +Plugin { + type = pager + config { + showwallpaper = true + } +} + +Plugin { + type = space + config { + size = 10 + } +} + +Plugin { + type = mem + expand = false + padding = 2 + config { + ShowSwap = false + } +} + +Plugin { + type = cpu + config { + Color = green + } +} + +Plugin { + type = net + expand = false + padding = 0 + config { + #interface = ppp0 + interface = eth0 + # set connection limits to make traffic graph more accurate + TxLimit = 20 + RxLimit = 190 + TxColor = violet + RxColor = blue + } +} + + +Plugin { + type = space + config { + size = 10 + } +} + +plugin { + type = volume +} + +#plugin { +# type = battery +#} + +Plugin { + type = tray +} + +Plugin { + type = space + config { + size = 10 + } +} + +# Digital Clock +Plugin { + type = dclock + config { + ShowSeconds = false + HoursView = 24 + TooltipFmt = %A %x + #Action = xmessage Please define some command & + } +} + +# Text Clock +# ClockFmt: (string) Clock format string. May contain strftime conversion +# specifications and Pango markup information. +# TooltipFmt: (string) Tooltip format string. +# Action: (string) Shell command to execute when clock is clicked. +# ShowCalendar: (boolean) Show a GTK calendar widget when the clock is +# clicked. Only valid if Action is unset. +# ShowTooltip: (boolean) Show tooltip for clock. +#Plugin { +# type = tclock +# config { +# ClockFmt = %I:%M +# # 2 line view, time in bold above and date below +# # ClockFmt = %-l:%M %P %a %B %-e +# TooltipFmt = %A %x +# #Action = xmessage Please define some command & +# ShowCalendar = false +# ShowTooltip = true +# } +#} + +# 'icons' plugin lets you customize window icons. +# these changes apply to entire desktop +Plugin { + type = icons + config { + DefaultIcon = %%datadir%%/fbpanel/images/default.xpm + application { + icon = gnome-terminal + ClassName = XTerm + } + application { + icon = gnome-terminal + ClassName = mlterm + } + application { + icon = gnome-terminal + ClassName = URxvt + } + application { + icon = gnome-emacs + ClassName = Emacs + } + application { + icon = mozilla-firefox + ClassName = Firefox-bin + } + application { + icon = mozilla-firefox + ClassName = Firefox + } + } +} diff --git a/data/config/pager.in b/data/config/pager.in new file mode 100644 index 0000000..0fb0de4 --- /dev/null +++ b/data/config/pager.in @@ -0,0 +1,28 @@ +# fbpanel config file +# see http://fbpanel.sf.net/docs.html for complete configuration guide + + +Global { + edge = right + allign = right + margin = 50 + widthtype = request + height = 58 + transparent = false + tintcolor = #ffffff + alpha = 59 + setdocktype = false + setpartialstrut = false +} + +Plugin { + type = pager + config { + ShowWallpaper = true + } +} + + + + + diff --git a/data/images/Makefile b/data/images/Makefile new file mode 100644 index 0000000..92c2d17 --- /dev/null +++ b/data/images/Makefile @@ -0,0 +1,36 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +IMAGES = \ +battery_0.png \ +battery_1.png \ +battery_2.png \ +battery_3.png \ +battery_4.png \ +battery_5.png \ +battery_6.png \ +battery_7.png \ +battery_8.png \ +battery_charging_0.png \ +battery_charging_1.png \ +battery_charging_2.png \ +battery_charging_3.png \ +battery_charging_4.png \ +battery_charging_5.png \ +battery_charging_6.png \ +battery_charging_7.png \ +battery_charging_8.png \ +battery_na.png \ +dclock_glyphs.png \ +default.xpm \ +gnome-session-halt.png \ +gnome-session-reboot.png \ +logo.png + + +install : + $Qinstall -d $(DESTDIR)$(DATADIR)/images + $Qinstall $(IMAGES) $(DESTDIR)$(DATADIR)/images + +include $(TOPDIR)/.config/rules.mk diff --git a/data/images/battery_0.png b/data/images/battery_0.png new file mode 100644 index 0000000000000000000000000000000000000000..0e2a82ac83f7109c89d152b0b061bacbd9aa52cf GIT binary patch literal 4150 zcmY*cX*iT!z024$}&{OlCkt!vko!hWe`IchGEjkAR4l~Wj889_7)+_Bzv~3 z4ZX&`OiYnij0}w|$x^=Q`+j}bdCqfR*E#1t=eh3ZoO9iGvYqYa(+-e4Mv4_iiEw_`IzzBY?v{{VgO>kWE#3zh{%H&gGV$Z!|MG#M zZ)9fx5Yn?km^k{+PUg9h-I-2(hpg7s_Kx=9@8nC3eycU(AC5P>G&qb>h2tdbte3IS z%u-9Mqgh}pf=H;xId>ctZsR0&j(7ij=GzcbSsFiDt!mo7J$@8OH4i(lV3dvB{n&$4yU%rp=^5O#4CmjBspZ0t5uInEE_qr#)jtvY9$U<*0 z8dcl2r`+b;(dgZqB@-m*Uh~wRW45pLQ1g?yFa|-w~?IoHdZFtyIW^!}bNJ#kE zpRXC|GyG9w4t#Ez(dhz< z9iT0EP$Xh8%lUJ`{ypv4+Y8liciP$W+jP&quj6IJ-v^|v{ixe)?Lgx=gJF9oaU2c@ z*Vj+Y&6U()KF#d@zR*Y9_+sS}AdHPmy5=!}utUlXXoR-9XZi{-Pn&{>B+{;7$wc^7 zXXk2FZX`^xB!boMp+o#Hj-!0nGqc2?%|UJK&bLpNu=&;LIu}pR%OEZqIa9=&43d+j zf^Z2t(o?RS9>3t>@@K9rymAV6U0t2awQIeUrr8wEUim!W`CG#ya_wRq*dTUW8TPpr zuwkXQuI&5me`oVGbTBU)?WGIKVTeRE!-M%b>S*z0VJw2tI6kE{Xg+KP(uw?`A|1J^ zUSJd>J)}z;9Q4BD1#Y?bL>k4fg|+77=lA?K+pZJ33|?X~YZ@C96pX|7zn!Pkul_!+ zVU-mC+}_&OJ-E)6adxioB)$C@$r`<+T{NBjm^4-0j&2UxTJlW92uy0&^_IBh+R*NIm8}J zd$s%(`|8BgLYcVY>y*|gCT5;ThBv)i-!Ee2X2*V;;oQyCPx|3G26akx+4WZip$grZ z-Y+2X-sFjdq^h6-@e|FJ3d#kcxJGMoa$3WUI)lY{YX%Ni{B_mxeP-Sb`?lMwPxwu# zp)D5{&6^ve<`V{_V$5SZ?yR3Y*bVovuzD9?;c#(k#uTQCP*ARvSYHT_QDP2N!%og# z6G#F;yx@P5p5mo+w>Yx@#^g}bGn#*CABCM=C;&r6fY(!~d88k?2AoWlQo4rLXI{XB zeM7>6NC5EKT!50gZ5!@Z=tsKzb@TzAfT1L#izIOgNgzaLH~6G6kUJq&QlhWP@XtRF zoq2j1`=I&=kYaG2dHNIvqM{Nd5GD=-XRo_%C9?< zj5#y$3LBf5O!SAQ+l#q$l8Vf6R`OT@Ag)wS9&8Di*o#%DxR1G>2YY8TD=I3Y(b-X( zh70k^mpff^3}nLe3cc*b(vw`$Ox2)v24fE}D=yv3CbCjoiWJ@9P1&bf5m8?V-0#2C zEhOqVFON09Xz+%Lr?%PoPJV5zmAX!;mN5=Uqvf@XmnRH01F>0GpJ$#N%X^xnoy_$# z?b&%i4&YNTx35Ry&*gFdi$E^01&m)J`+U*0SM~4|(|N&_P(9YGDk&`OBtX79c!g13 zVd9iklxrR^h=jebJ4PxRDD`mif7FRTEm?9=pV?NEjjtd{of4AlI}W^X$SPHGN4cKG zlGO@}D;&13eRah)0Z%jUUM!^`>1vfqLFDIYxRQD?bK6&nvTtsyn4Zeu{zsv--wB-n z#!9iye)C@+XN}E&0l*|+abWCtMeQ;5+v?QX zvAmP%78a+FhZ$i^s6fHqW5Lgm8{VoTc;z%pedZysh%NS;*jKx%-&>X86X=g zS{CS(kb|0WB*MS8or~f@*kzTxue-7Wd44ssYVR_7w|X5qTXrc# zYuT~H8SF!%Qa?C0n-(am*ljf@96nQW2WohBcgu;Ra3v>t@h6rBTl>!kIGE=0SCN=6RS0bis;bq&cek@Py^&o*u zRx>W-7hlHh9Je##g0K{*FM7z=-qQX{`Z=XH5KLYUG()GPpGGXGhIaQR!%7eWrV3XS zc#ADFp-5OMgok+SKL#$x+gQ7p#owQ1;jaBTZbmUhuP%s`yH4Uvcf3ITo>8$ptzZ=TnGZZoy&a9)iWVu4qV0aEsmj z`|Y;U&rTZ$x*YGvnx>ZM*nOel`P~?juw7|a?CI~bT24!DjK_i3qZ3IT+Kfgn;fogKFU0Kw z2}%|srZ~b2YpUS~u_FKmXJuuD>`i&u#U8<+}xEG z3r1r??jzKS1dsm;A7UMvfg)QdpQZZOSZw-#v}L1}z`|Cr^rR6*ZVAaNa=aU}4j`{! z3JRH$dt9@!erbO)Rkh{vNaqe!sd3>C!{6)*Fh8+)^ zQZlr7O#O!*BF>wva#Mb3E_XelQrnDdQlFFqDNa@&!@xNUxKeIaHWV#QpOhcD zn=2nX!roa6(wX=a=!~cMw*4@J2A4PDMPrX8nmN!f{@wPio&MP3ZqhlRVveoMZl82* znwt3;T~|}d9R~)RCIQ|Zb4~3Y!=bI8;6*9I!sYe$Ru_)g$?<;Oj=<@h4hDI7x6P`Y zXSZumKR}ANXUF&UTJVf19D2$nV0?ctv0CsU%G8~1g)k7FJ}PnH!}RntTzWvUBy;-M z>*;;iS*dClM$4<|$ZAv2UPj%UoX{pzwSl-9Mur){lYvx9xD|QFU{7Czyd=vh?Ji4+ZJaSS=S;S}x2q4z}-1 zMczaqXCfasl-b6Y;&XCITJ^@gFKdqoa}MalT3Fi7?9?sF6rD~0nFLVy(b@CLp1Xo? z_a>_Yk7L-g?LWJOq5<41M&lrstpE7&Q6N)_dXXq_5dn1X_;Y1?w(8ngGHq*mtGPG|IweXL<_^A_#<7*|>3b%PXq@LRG_3cl^;OkYjvv+#@$6KWjeOJ(l#+AZ+zR+Py9yH8pj_wI}jH=9r_D zDCwG@D7F^^H+H9wgKik6_J_LRaX3Cw(L%AzFGY_#-_5mbHo1UZHQu1%OFN%S^8;NE z_vjsOx7KdP>`!qdpa0x-&&9>1^W(?UKh_x9NAl{y-R7~RBF}n?580%MIlffTb8ui# ztJ0G!dVak}<~P{d+8VR;2;l3mJ5fcU%rD3s{#vNzFGQL)!QHa51px2N~VNS{72 z%HDa_P!v&HQBnR33G0QJAYQVhXa=9mDp^t-X_t5Q{TV0C?J<9J?T9(_877E#jzgTP zC_En{+3mO2yZE1F?y<76vK(AlMn(Dg`)T^@wvc^}$*l=t+HOU(<2j)LB9thM?QITW zuG2J!F}H4s>xX~q<)nw1nVHBiCFatSUG~{p6}Q3715d7lBe87H!Jn1fK&N)ct_|3} zZQ;It5$6}Exdc1_kKPm+t!9#<8LO+SchK3BoDJ;AXk4EyCb-tWjSmTIk<^*h_;ka7 zXIXSq61e#e;+dlUdpuqKvd8{MrlGUZ)`%J1`*4yg)*kitU}FHVGum4&?)#H?uze3? zh7mpznry$NQ+>?%UW2}FcVQU9C-_hidQq6dqr?B13D|P$$U%+HoJdqwYv0Ob){5Ze=Zo_7@*xr5`xx6h zrS~AcNs}fbO`3v$BIV6}zrVh-JNuk7XP=#&vom{U$hU5oa(!0KvliU7|39l1>+B zTop)!0or4JJl(O(VLY(=48OBiGn7_$_-S&1#;ir;Qd2Dj2iYGMVWgK6(GaAVnOz|@ z|C-nlk7&B-Zt`%9G|!6}>1!Em(g{=)I+xws%mRyMxo3D?8aDOIb@Pu-u>0Wj-JKdM zG577+4Z`!SLTVV?e6>HbE_}lG^_g9GPLsm-v*TQ67yEMruOwT*{ozeOkr3n+aeR8l z|9LS+%;W`Bi?o)fHXpL|{rLFUt7Tyaj#~Oui$ZkrBnn}ns4*Mft~XTb>0p-DrGuS? zGv~g(zIfQ)@sFkMvtR(thaf6E{@vGOc~eJ4Sx>L!Ka(3<-s$eXl>2un_cu7zX8ro| zjAFJO9UQK6iD(kFw%;?waEZ|>92)@OrtWWOcIb+ru(}_xI+)dQe5_3ZSz1~y?oT?O z&9}F=Kfa7jA+71)w;F6y1~gXZ8%_>ptery^qeB+kSvF_u_CA$nKRyjDl@Za4IQ}f9 z_q(TMss=xqG+38}eBl76!4Xh^mk`w8s1x?H)MugDai?xo^rY9iIltsa*698c-G3`A zMN}*3`a9o+3ZQyyeXac6Jk{7@Eldj_l7z7REch*1oULxN??SU)%W8JG&+iqr7G%z! z*;U=)$*)gzuaEIc-ON57h-f(re%-j+dm0@b-Pe90#;B2gRgW3|CMT}g*x2`ei;T5i z(~Nj|)+Y==uBQ+Q1c(y}d#gho;g88eXcg;m5g>>orb7#u-D!G!LYB(@WdtZV*ASf| ze&f_vPk)u@ovbftAI=7ZY}HKqogRnE$;;<0>=bys9=X#Dk;acgH|P#(*hAPts>;`&SWUn`DI zyCROe4d&(#S2BFlxfu_(=bmWo9P0>c_%p*yLUyDk-~(57j6m;0en3RSH(_*F+caXP zr)~QO2J$NK)!tf>TR{z}Sy^54I^llbKO4{^;i9ap>>ZYR`ylPg+N$qj!~5tuc7myM zM%U}ZQ(S1?(bV2@_DtMn^LjhlX~nk1C+X#Q_Wu0XFG3AJ@`ZR`)nN|88UD<}Z6qf%tN@eIvwfMXb52bxZ1KxwJ<;PHy%XWbp`?lnxp*dz z8Pmm5yLF#w)v5i&_^1z`%g^iV9WI2=U)_IHiJh5%4&6>F`r8)Ifkt9T*g^qGSISo- zgq3;^d(12*o$rR$m_6Ji1&X>d;6Hf2+j2}9w(fi~xAgg_ z&x$>NewKhn`>y`Y<=da> zK`JhP&Bnegu--B-dszf)s^{(<1xLB2+$EFrF?>{RzEbI2LdB#Iwnj^5YzlrC@+nss z8~>UQc&072nh8Zh0M`^8?dA9z={}XwG(6jfUB=|Ph?1G|y?hc)KmwvNq0dW}Nhawd zZn4O5e55#7ix?a5zF4*8wGh=X2Xf)(1S9p?rJv<1;?}3^`oufshEN#HNtN869=?_H z|HSL3LwfdHO2!2FjlpEIe(9f7c1g(->AytG1)4wzNdQ~^HB^+hvtY=y2_^1$w*9MU zkgxVZT)B#&n``2w;xf%g)>cONtr{j9d}0xDZXIp3>Hvt-gj8rUw&se^j80&f_VFTt zhrnEDY!QMj<3<62j@Oo2ED5(>-sr93#mm{_4HwzJJsV{%6izbG6Ez*$EaTWxMiqxqQDp+y8fkxjV}`Dx0Pv>-UQl6T0U9T0NYS3q zQeAhR%#wmU&@4(BB&$5Vh&Qs}rD?oN;j2(V25G1%Z~@$9D&DsB2AC9yr&dW&K5RMf zV4H?%s|iT;>zk1VVO!0tI4)Q(La9K19cs0-aRbvs@sS)PIR-D=F9X!hm^{(qzUd4@-h^5(tWyixEy=t_vI6VomIblO=EZpYZldu zlh-yI`J&LF&M-^%b~-@W_=Dh+JVyovf2v(J2UPzV8~IQULDy^dy+v_M{BUNxh$fZxG>SA1jQ$5i`Trej7z>H7u81;c_>26CD%Wp;_@eHN!I;^Q(#UxT8N@X+j#n^5 zeTK$%oC#p27b%vLX@ z^TD@xz#wIqF}n=nDTtMo(Z)y-(JA5Qq0=SsaWYT@+gjZa7g^TJneM{GFwVRzhpPUd zJQz!H%(h6xC4yKtESbCpBazI^!_smV#J_14uZ=!{B9rfcxJQ39a_4f}`8>iKUM}?f zrb#3;9H1vJd`~la8*<8~$NT|)YZ2bFxGT3Z4H6$3HAC>fM`&IiddM6db&e##N;W_~ zPhjT&6M+I5YCHT3nu*YOI9jWFP9kIhjsh`QYrc@3``OF@Rw2W@I4!b^9Vk$u97^(} zeU>dF9{?cv{U&Khj<(^WjyMomQT0XYbE*(VWj=g=zNFtl4Yh=&OmYpYs*pb5)0NO( zDOVia1Xn|gZPRzn40pmutxBVeX*Cb#Tx^3=Qqx>1?q0lDIKfJthG>~86J?UE4RJ?bl3>N>#9R)tbKmX%NHf!A zUTS)`RqA*ddp$Mn=tMVk#}dL$OFi&E?_zx^vN)2;3OG#Ga7u8OVQgfn)K74vgCnYA z^!EJKt1DZ({-R+`aub0(GZU;kuYy;u&(+GC;-lYE>ZD!e`ndb5IO)HMh2pdJD$?_Q zxBv&`P`73x48H{(s!(PWh43&CPyJGVUb++ze)My5cYUnky5mk#+Z|Q8t0^vlDzSLH zXBqxtlT=(Hf=`$+%(t0b>-m?33j~KxD}N3f()nk3cyoG8Y(8A!Q-SH*h}U-?&HP2K z(M64g6qQtRv4AARDOJ)O5xYs3uj>BU?EY3$_&~I3Z}%f*E&q}^cLW5 z@_56FHvLvQ;&`cnwcoxfU;iEl1;m3Dw`HviVs?>-B3n^U)^wskjW7?>K}pwY`3`E* zh8eNb&M&=vGEhl7*ynQNA#5mvUi`(;i?Ex{;8utAV$$a(HnHU5$-Sn!y26ExJTlmc zIXg5tmFhv}9_4l5CE+w+K^G*37L%Ac!HqKM1FX3g(Nc!jd8%RgVv=^?i&)|9?DY17 zt(n(i;zI$m57rXG#_L<^dt>$f2=d>|YX6p|yrv+RD|o>)woZbQOEECBM69;r$Tk>I z)&)b``~js9{9+_|sYd20wX_>Rfg}NaroC1)U-LoDYsVJi!RGXnGi`rnIi>G3#D=$kxT3uFy3B^@s|E8lW>caPGh)Z|j}f&12E6|&jO`p9<$35BU(F>R zw=aeP)9z9vxo7y9+?la*G2Aw8)xD3J0O0%nQg=hk!H@W}wsQM-K{(HIn>yD33~2aA z%7}Z3nmxMf8Au2Wq9GeKBD*ar5u^!72$wor25!Cg+qb4C5oa^lE=RG}RlT!NZi~<_ zo#c#_G2Zb??64D8M1*KB<)c6;h!)$p!*++pt}L{I+)vzB1`|PMJgMuRXszJ(poaG+ z+YR>@P4x`lzW)UVek-u}Ej&70q*tL%e`spWu*08YHx~}R{N7Xd`wRpvgh~Zb9>Z}a z!WT@fCQ0*>Pk}h_HyyW7K@WQ?XL9;n{O)3a8Lf&?_sDvN6*QawH&ROV$aVvvf2-gHl3nUn zjwaseBYspgY=;G@3pXbeue(5wL?B1y+C#oMX=&58DY%-z4XdRl)0x`u&trUbG6RwZ z^sRY8-VM{C48v0S=K-g9ShE23c(NmgtK}$K;bC6HT3$>A?Tm{JFv0B>{o}+)I2th( z)46|ka+pJJU)+kRGsbQ?q*=J`j%%W=#X9v~9(7~O6jM(2;B18u4uMP-KNHM?;Dwm; z3eCS<|4i8XF3trnbS=f}MPLS*TH4R7LJx9s7-TyVin#4AyH?*F$}Tb$4Q1Zj+Y4KI zuC`0>nC$WUo6~P??Cg$S9&eukjbquG?_yfr=xzL!2gWuJ+*}^uG_n3TIsi(FFhzMq zIDMiO6*b{%n#wR)MP*GzMFo%Xwf{%(_I2}g52X_n)#Vjc<(1UYFqo!_il(Ca{}Gn$ z67}f>!GA4S`no>|cJapn25!EtIB}%6%U#@UoQqrN{T`e)eG`B*zJaJPyc6|bD=6zu literal 0 HcmV?d00001 diff --git a/data/images/battery_2.png b/data/images/battery_2.png new file mode 100644 index 0000000000000000000000000000000000000000..358d8e6ffab80cd7788dbc883fcb64e9500839c3 GIT binary patch literal 4243 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 literal 0 HcmV?d00001 diff --git a/data/images/battery_4.png b/data/images/battery_4.png new file mode 100644 index 0000000000000000000000000000000000000000..c13396cd92504144570ef9c8c389b8a196f75004 GIT binary patch literal 4124 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 aXJt}lVPtu6$z?nM0000`A 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 literal 0 HcmV?d00001 diff --git a/data/images/battery_6.png b/data/images/battery_6.png new file mode 100644 index 0000000000000000000000000000000000000000..b4869ed10e07f3ea1a4875a311175f8859b9c9f6 GIT binary patch literal 3914 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

y2_v%Hl6O*~jd)X&0gD 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 literal 0 HcmV?d00001 diff --git a/data/images/battery_8.png b/data/images/battery_8.png new file mode 100644 index 0000000000000000000000000000000000000000..1ba42a038bd96f19fb9893ddc96f33184104a982 GIT binary patch literal 3684 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 LR>&%ZN6dc#sm_%E literal 0 HcmV?d00001 diff --git a/data/images/battery_charging_1.png b/data/images/battery_charging_1.png new file mode 100644 index 0000000000000000000000000000000000000000..6ff1a754aa0edf08f4771d322408979230606d3f GIT binary patch literal 4592 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 Jkl=Q>{{dtXeq#Ut literal 0 HcmV?d00001 diff --git a/data/images/battery_charging_2.png b/data/images/battery_charging_2.png new file mode 100644 index 0000000000000000000000000000000000000000..425e7cc5be77361b0387c3fcb6dd57a171ca1b80 GIT binary patch literal 4543 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 literal 0 HcmV?d00001 diff --git a/data/images/battery_charging_3.png b/data/images/battery_charging_3.png new file mode 100644 index 0000000000000000000000000000000000000000..2e0ce6f932c796c33206de88d37422c8f0fff983 GIT binary patch literal 4513 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 literal 0 HcmV?d00001 diff --git a/data/images/battery_charging_4.png b/data/images/battery_charging_4.png new file mode 100644 index 0000000000000000000000000000000000000000..73e0787e1c6b8e45aa2e6cf74f3917cfde6c0629 GIT binary patch literal 4456 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 literal 0 HcmV?d00001 diff --git a/data/images/battery_charging_5.png b/data/images/battery_charging_5.png new file mode 100644 index 0000000000000000000000000000000000000000..5dd3385f39d0a1f30f46acfd3494ce42bcc91ba1 GIT binary patch literal 4438 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 L%=BOBI>r4Dw5Afd literal 0 HcmV?d00001 diff --git a/data/images/battery_charging_6.png b/data/images/battery_charging_6.png new file mode 100644 index 0000000000000000000000000000000000000000..fe2d3c5ca32ab930152bfdef026d2d56e768d131 GIT binary patch literal 4345 zcmZ{ocQ_kf)W;)MtO})Suc)nx+A{=+mKrf@i_zG7)+ky^t)MlFptZNQ_NZApw6RC4 zXspEEyngS0?;r1Zo^wCXbDsOX=ljR~-wCaDwKyM9~YyPhuFut8+1_01sz_m0? zTo-=jxFtFL3K)z$S_oP+`t|EfR3n*(MyZHNUcr0piq z8+K~7Fnzw0m?xwwKeV$;0&K~clCsN%$WM_dM7hwC-n4riT~KyZn$s7?@$USf1vnFI z8Tvv=KVLDx$sfHi()6meI z5`Xt`sDxtpbzVs-C{T5i(kpjs(yR0rsDuS?HBY|I&D8{HL`HUnT%N3Tsi>)4EgFr$ zc>6aH@BMr@3{Vz_r#;Uo&A7rg?PNi2MzudkYl zn%)sgbS}ljfqO|wK-H$AbHU5W_U`Ut97=&=;oE+NG=c^Nv>Pc(gm!6N;l77*^GpK+ z13>WU>1prf#hGSKnSBIDHw`bf@Jf==`Wx*MpT$?4N|*np>K!Ma;`MA9TNj_6!6K3+ z4vTR)%Y!LhT@(25gEuY()7P#e;ZruXgQB(VP$aUdrlz5;uKUXuT1`z&q0)bWZ2%@% z^br@pk#=HY0)q;PW`ly@AbXD6go99>XsgkN@XO0H!WHKdzE!NKyzps`*r@8soZr`r z3;q=4-o~%0qx<_ZFJHd&SLCdyP*jToL*UTHX(v0osGc4T3JQv$23lc(!4&Sb&FMzh z>Uta_^uvd&ICTV^W3b6moGqEcBFjFh-p|jk6(QVJ-_*2^9fJzbn{PSN;w;;bOptN79C53bs+5nv-x)pW z`yqq!_D=PfZSh&^CJ7`gjIF6%h3MzWN8c+0&SV>P!$=Yv_=a;93%8~sw^x6O-|aju zmKmt?m^EEE8Mj@DXZP&3)U(K9zrHo(Uz6`$_w+rQ4{H4IK{pGPl$0cS|30i>%>>y@ zGW(?YHY=B$k@XN3Gdwky_>knXLft$wvP6u!A+m{1`uMjC@qT*x;8k-#{QbHkKExUtV^#_ZM>A@}hfNO6%Z%_l>JWYrJ+_#^fdp zG>m_PJV{x6Y9byu;veoWk8luotd27>VvGUx(706B;T)=&OY8gHR+nY?hjV!K8^6g( zGF1hr{+QX2_krhpyo}ahKcG`$xsUngcKS^4vYK6s>7ZFE8=JgRG@Q>8aG4IUZTC=% zJV+LFd*NR?Jt@|Ndyf$T@%EnopuXF&`^4P*ahB@wSf)CN2h2kT1u>ymYr+VWq4SsQHy@=%6EYSvNI641gva-x!+(6D$Ypj^$5tPF5amV5ZQK$ON)b0=~JCmi`E| ztyDAkgEgo)bwI#eIj7|l_z5b!vFf1XB`VeKnXWw>|F(}cNt2m$op&FOis{L^>HO?R z_v&Y8Ia#ch2<=8CFPl3jJKyz?nc<_gQBcprzL4M8Z?+6cFEd5pojLcw>LG(ziF zwZIxe${L|iVlemLO6iX1$?u|OuNdL zVl?fRA!%dIjT5diir-PEae6xH=wb-I`P8yQW-JNJ_hN|peb~srnn}@G29F=JQ)y}@ z)ST6uvHf2&`|jMIaqPxv?5~?cs~pMb0Gw$ zTl65usp0$PhEuY$fpEYI03qy9z2|H@qZjXTb5i-fy6h~E+u+H!EM2*HNN??E@ub@L0#(aY{K5wQn=j7U4LVyh={R3oa-$^ zCTnta$Z+u`lwLo~XRNO+)-s_K$g5HI>>ajC-(2?Y8jykMB~71+seOdH?o`MUN40%) zHDo-F#>|lftc8&;O-E$f7ucLta4&W^D6*%B+Or6gDkmH{;bNja-(BEeRO zBa{t$+4BIggZ`A})~qlgI-7J^>XuCl-VL)U5xeBU>E_tQ{b@)B_Kmiy&YZ7IH8@#y z9)3`#VV3Q?_XEK5je0`YveKs_AEadz{pc;D0mg>RlFZbGyJ!*}9UJB3yC&nn|3Q z1pdTDAyTkV)@rMRby6w`^TE_k%>=eYpvPA7I4+Hp&4-i3i&jVn+x)V`3Ar92;U;0r zsBW_!>&Ip0Fd>8%v~VvW^S8{k%=i}n87x2o&G`Igi-$kvTN8yTo_h%Yfc{55D^^8C z_v23k%ur|*wnd19Q?yuj`Mr85Uukug&)MBeyPqx>nr2+lBjnEIM8~%D!1Vr>Bz0H- zlgZ|zow$wU@C2BMz#kxiO z4`J0oak7YahlLj_g7~ZMLR$rO{O^pxSMl8KMEZPrc*PJDtD(i?n3729a5DyzUiv2A z1euu172Cbx6Pi}Ge{iW143{prgXq-5s1wmG6mKyDak9Ko`!gUx#jGtSx9}AJb0SOm z6&pT44wsO?B-D{bTCal-?P`}m5o8)`Q=VZ!7F~BRT0@|E2@~Gwuzm8=c%_3cG(;c~ zD8P)+Hn;u5sau(BR%E595gEt~@PYgbh)**OY<{5k9tXi{JW2WayO%8e)dk^-w{0<# z57+QB^yL{&=&Nb~r!IVPiTe4%l?>up>-CX_U!#~9jI?f>Er|)0BvB#1eo8bItKr~D zU_#!IpMI%jglH~{B!`AzX@?#zBUYwc2wzU}07(HuI`LFc&8Q9?1F3qyC(Crux_TxA z18;(4O+PC!Q>s)rY+!}f$*i7H?k0R%dMI@t^8g_{IpS)fBwFm9#u#|X%uBlGA{eq& z`V1rtXScqwxDP>uCmA)(fSuaD0%-3-@f#%UxU(u^yxtqR2sfxJ;v_yL7!~sWs!xS7^$>g0OIbEw~$#S zRt$|6i&0A)y^siM5TkMOj(Y@`00>X!(}M76Xsg%B7AyyWMgTqOXYIRl8sHdtafQ=S zgH#BGaeh#E2la0>fPo2#!~x>oA*?cyLbY6PMSPor1biOH@_?m&GNf{0Z5LDB<-3Tz zkh@pGQDEi9S^^4X&%`_9+WbW{1%CQjU%OVxN@@GaS_kQGvR*VOra`2he98z@D!$02 zp2p(pRgsG45R7?u#KY4|4uF!~DvLi}FiVlVdJ+RBnf^r4M5f#U8>PR(1CY~ySJAe8 zN-06jdkZl{;BWpI77Qw33|5B{6175Vzit)e^>ANd|3oy6Uz$9->L%yx0I9!}3YB~E z*b8G=6}JQm#nkgBl5#6Y2X)p+waB_?gi_Vtkr&#f&KP|=@`%kP`K3hUO)HyiCqpKT z3fhBVrN(H%i*3|veLRMRHbuM+y(rdGy9N{YCMeGybq;x*FLb!iMgq;ErJF5CTYxTP z!O2JVoB-p~x!dTxFm?dS!pOh6`QwO3QF}G6cN@5HMkgL7N-XZ{3{rvRn354d3xD#< zyCO4fPsa$7Ez2@xAT;wTbgz_*{kQwuf2{7jG1cMQ?!glTu!JbDBQ4TEK6iBP5YtNj zp*bQdaPucSF-vl0U#nR?N`4gv(Dk;SH_rm8r1TZB}5DaF;Bi<(2jmW zg_KBXRMj)FzO~fA;647^G@}}0_T=(-El#53~ zeaptL9~TtvaJ{yPs0s}r8{xumN?OO&%Ao9rXIsWe&~277C%r74-Ukd*p8`b73r_@+ zcIF*m78$r|t+{tWsA2Zd)8x-?;yu;XoMM%G_aimWHkjYV$Y+t=G^Ws8IGm#PdbY8& zcrMyL3JhO2F8mYZb#=RAJaHW(!cZJ2iGP-=_-8d_ZD(zkw2J~I{&zH{Sed?V``lTN zEzwRU1ZnsaP(eK)PuOUiGot|zgCh4n@uV zs48w7`1do+*UnfbM}tuWW{7;3S_IRq6{`)$&TfU%3DvMI-7GvNgT5HJd3DlXbBJ*c zJLKi-c+o69)oXMN;^wg5^4yQ(0iuEV4*?6lopIg=VX$xg)x4(@;t&fp!t@yfm(m^? ztUj17wOu^Q%JXAf#Mxmpq2p-beQo5$24%tdk=x?>Ob`=GaN<`>FZ0f#keRnf3#*_~?u-s-3i|14dw5#d~>mpMIO|_B}zo$IK z%zC!b39p!~uC09)F1_@2a{5au*GU*CV)y(I{r$V4shOEv)dW6uEo9!%G%GHSx$|VH z)!fq3!QVg0rgn69mzwu+FgnP|DOS5ggJVz+o_w|%3^w6(V!_-@&~t}$2<3GMvL$Q#1qYjFq2Nd)1_rmtbh2pKLD^-M-Zu6KTp;D- z?WZNSHy`?-x4O6#eE;^Bt6kvFUny+|i(Ymq0Dh_|1n&+DlI*r^f7(c}^6EH9xPGS% z^2XjHT-+dB)HE++_n!7sE>iPid1DWDJV3lG(57herGOn)?C$*Egk>@*ul@sL# ze%o^j+iir-*1yW<9Ff(##2~I-^5;y5+s49<)rw(1)+M?PC51@D(Wr literal 0 HcmV?d00001 diff --git a/data/images/battery_charging_7.png b/data/images/battery_charging_7.png new file mode 100644 index 0000000000000000000000000000000000000000..3df5ace92c7bdc881079443a752606a8851368d5 GIT binary patch literal 4243 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 literal 0 HcmV?d00001 diff --git a/data/images/battery_charging_8.png b/data/images/battery_charging_8.png new file mode 100644 index 0000000000000000000000000000000000000000..6fd01ccd93802006b02872c96a11db787e52625c GIT binary patch literal 4074 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%0A3Z(xyoGW37XSbN07*qoM6N<$ Eg49jADF6Tf literal 0 HcmV?d00001 diff --git a/data/images/default.xpm b/data/images/default.xpm new file mode 100644 index 0000000..288cfba --- /dev/null +++ b/data/images/default.xpm @@ -0,0 +1,35 @@ +/* XPM */ +static char * icon_xpm[] = { +"16 16 16 1", +" c None", +". c #323232", +"+ c #535353", +"@ c #4A8A8E", +"# c #DEE2E2", +"$ c #7E827A", +"% c #8A9292", +"& c #D6D6D6", +"* c #36767E", +"= c #9E9E9E", +"- c #FAFAFA", +"; c #B2B2B2", +"> c #DEEEEA", +", c #464646", +"' c #5EA2A2", +") c #52969A", +" ", +" ", +" --#>>>>>>#-#-; ", +" -&%')))))=&=&+ ", +" >;$@*****=;%;+ ", +" &$$$$$$$$$$$$, ", +" &;;;;;;;;;;;;+ ", +" &;;;;;;;;;;;;+ ", +" #;;;;;;;;;;;;+ ", +" &;;;;;;;;;;;;+ ", +" #;;;;;;;;;;;;+ ", +" #;;;;;;;;;;;;+ ", +" &;;;;;;;;;;;;+ ", +" $............. ", +" ", +" "}; diff --git a/data/images/gnome-session-halt.png b/data/images/gnome-session-halt.png new file mode 100644 index 0000000000000000000000000000000000000000..945ebe8a58ca3f8265fbde0f16d1a34ef576d74d GIT binary patch literal 1327 zcmV+~1Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2iXQ5 z0XZP@OZ5%_00gf|L_t(Y$JLfiY!qb}$A9n4?C$K&Y%K-0p|q8vg)UO5F$NP7N`eOh z@dC)n3yB(z4QMc6A{SyLn&=r~0z?jgUW_4zgFrkqF~rc6mL8}zblFO~<)iyCJMWi= zEu~U4dNNM(KfIaD^Lw7>ec%5x@PB{A-@QEl)?4n1k&#`F5SxYPZA0r`gh06Nkd^X^ zfx+o~{?fh+7xcddT-~@aZ{cFt)z#UwetjlrZ+HB*HV}fSSfnyJX&#P_)+!SdzX@yi z_74w_{v*IKaJFvPaM(?yj;z_UC&+en2{}EDtya;M3J8H4hB!fhw{j(Ow{P2tZ@#JO zC^~*^WaQIhU>3)7p9Xx?)AM1rv-9Y>{rf{#DG?0~p-Lr8xlGIc{rEjSRIXh^mrAHY z0Y8@$x&HoS_0FBwTRqR5nVI=%aXe@7;&OL)-bo~mJimK)P#+l~8Xm^f>k!A-IL3eJ zCDNNVVdvMTRwKH3lX!HLHGB31ZZdh~a(8$BnSk@)V(rDw9XkSf_bzd<2&ojg!-r`( zZ~z;}3m@5fo#pSn$BKagL?VGK7SY8bojZ31SbOpOqRuP;vK<|}(yLZ=WLjHA?ba=f zwX8gNkW_a!!uQcqE&z;_IL|$YpU<=6&>@U5RL921e2*4B~j=-9OcU}1BxdUX)p zxr3_L3Ab*=U$+iwG!o}EQ2MRxmk0A|jfC7zx}OUc~eAeHOaQBqR>{dZ=*{<=~9_U&k;h-YU>H8(rL z+HH^HdJAPM)td-IqCx>HB~B&-KxJ$UEhRz-CXXKnF@LhHr7}F+$TAGkQeqxFz;A8E zDAl_ZU<|rm$4H4$3gI-0Q*qpAj}VPZvj`w-wMO->yO4yc)iA~^Jw?{qp=z;+^}I$l zN&%4S>_kh6mJ;=pyj#+W>L60J4D_jyz(M9P@KC0=wMTyNR3p%wXjPcof$ zWT}L|dNsYLPm^4}{F#O*A3R|A-FK;tkCSL=pi+$g_kmtwcHf3!o_XsXvk1>)c6_{2s#Lysr(FI7s65L}1(5wA zj&HuERD$X0J~x?ky&w?QT8uGRW3bkOcKz2V`s)2^^^|@ROFmtJ z0K&)G>$<&fdfw-LGMP)J)4rEXIcjQOQt?a(P>eyl>3Cwc_b|H(`iKmth4%bS;= lF}2iu8{~XI^`HDz`~d@+RXVa4j!Xam002ovPDHLkV1gz0fu{ff literal 0 HcmV?d00001 diff --git a/data/images/gnome-session-reboot.png b/data/images/gnome-session-reboot.png new file mode 100644 index 0000000000000000000000000000000000000000..5dce2989bb20d4d2d7c2179126cdaa7dff2ec891 GIT binary patch literal 1503 zcmV<51t9u~P)_henB(5_!O=5Bg#Z55^ec zgO(Q(9}I;=AMnZOlL>(sjT#BofJTi0BorhyCeoA&lzwjadv<2$ot?knA? zPxTz$Mm2hHTiK#5k{4i+yLY@d|^u^sfA2q)kIFD;5`v?H=d&N_|M_yUHD0b-Co-GE| zG>lG`;0p<*6r@y8N}=p{P)b2bg@!~Fd-v>!UbsB`(z-RRdMsuDO3mrb1FYy%-G14Dkzm*c)mcY zDL&5;e)sf8wkKk-wJTFCY$9tT2qYR4I<5}?>0P=qVkx2C3^ekcWWx0)vfY!@`Hwo< zSJZd3w`wzj?vR^&D4xy zjx(5z$vQ2ab5L=8NTnb|6|j&+ob#P^b*AQ2d5NRy|cGrP`HjtPeSr0In}!L?c9qAY%LOtC3_!ky3_G zVp&te`n}I{J@!d`ePUfS66Ph#jw>Y-jPqF2BYV}vCiImBd2x>i1yo+f5%*RVA< zD1rHx9dGJ>y*paJd|>TE?NLuCc!7YD3XBpkNm#(3Ed)|qir15lkR0oM;;J3cC&fa?qRfdnN4aU%>v z=X~tuxMs~>Kasxj=>-51fB@jv0zv@7#qpsby-n{@gqX>r> z;?XedvZcmujCpopW?=m1qaOnBs#yjgY5@TN2H~N2{oEh{J1?}wo9(g{7D7-!QAQ~= zMj!(po@1-&$*D>%GkYsD{9RxA^6}3A*j3>?fXco9;7&vUm;j;(nPy|>o*i1OaRZOU zH&LpuRs>WD|E6$@zj>vZOX**apH@;h0Gz7t#i~7c5TJHPLN%#X6IFo#fM2zvQhlfJ zU&mjE;&d5lu_g8Z001R)MObuWa%Ew3Wi4c3bY%cCFfchSFg7hRHB>M%IxsOhGBzzR zG&(Ra=(94U0000bbVXQnZEs|0W_c}SVRU5xGB7YXEig7MGBs2%F*-CkIxsRVFf=+a zFdwWqMgRZ+8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H1002ovPDHLk FV1h7elA{0s literal 0 HcmV?d00001 diff --git a/data/images/logo.png b/data/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a6c8741bfd6620acffcdbcbbf2519d8a744943ea GIT binary patch literal 14133 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\fR +\-- set log level 0-5. 0 - none 5 - chatty +.TP +\fB\--configure\fR +\-- open configuration dialog +.TP +\fB\--profile \fR +\-- use specified profile. The profile is loaded from the file ~/.fbpanel/. +If that fails, fbpanel will load @DATADIR@/. +No -p option is equivalent to -p default +.TP +\fB\-h\fR +\-- same as --help +.TP +\fB\-p\fR +\-- same as --profile +.TP +\fB\-v\fR +\-- same as --version +.TP +\fB\-C\fR +\-- same as --configure + +.SH CUSTOMIZATION +To change default settings, copy profile file to your home directory +.br + mkdir -p ~/.fbpanel + cp @DATADIR@/default ~/.fbpanel +.br +and edit it. Default profile file contains comments and explanation inside, +so it should be easy. For full list of options please visit fbpanel's home page. + +.SH FILES +.TP +@DATADIR@/ +Directory with system-wide resources and default settings +.TP +~/.fbpanel/ +Directory with the user's private profiles +.TP +~/.fbpanel/default +The user's default profile. +.SH AUTHOR +fbpanel was written by Anatoly Asviyan . +This manual page was originally written for the +Debian GNU/Linux system by Shyamal Prasad . diff --git a/dbg.h b/dbg.h new file mode 100644 index 0000000..a33e7bd --- /dev/null +++ b/dbg.h @@ -0,0 +1,30 @@ +#include + +#define ERR(fmt, args...) fprintf(stderr, fmt, ## args) +#define DBG2(fmt, args...) fprintf(stderr, "%s:%s:%-5d: " fmt, __FILE__, __FUNCTION__, __LINE__, ## args) +#define DBGE2(fmt, args...) fprintf(stderr, fmt, ## args) +#define ENTER2 do { fprintf(stderr, "%s:%s:%-5d: ENTER\n", __FILE__,__FUNCTION__, __LINE__); } while(0) +#define RET2(args...) do { fprintf(stderr, "%s:%s:%-5d: RETURN\n", __FILE__,__FUNCTION__, __LINE__);\ +return args; } while(0) + +enum { LOG_NONE, LOG_ERR, LOG_WARN, LOG_INFO, LOG_DEBUG, LOG_ALL }; +#ifdef DEBUGPRN + +#define ENTER do { fprintf(stderr, "%s:%s:%-5d: ENTER\n", __FILE__,__FUNCTION__, __LINE__); } while(0) +#define RET(args...) do { fprintf(stderr, "%s:%s:%-5d: RETURN\n", __FILE__, __FUNCTION__, __LINE__);\ +return args; } while(0) +#define DBG(fmt, args...) fprintf(stderr, "%s:%s:%-5d: " fmt, __FILE__,__FUNCTION__, __LINE__, ## args) +#define DBGE(fmt, args...) fprintf(stderr, fmt, ## args) +#define LOG(level, fmt, args...) fprintf(stderr, fmt, ## args) + +#else + +extern int log_level; +#define ENTER do { } while(0) +#define RET(args...) return args +#define DBG(fmt, args...) do { } while(0) +#define DBGE(fmt, args...) do { } while(0) +#define LOG(level, fmt, args...) do { if (level <= log_level) fprintf(stderr, fmt, ## args); } while(0) + +#endif + diff --git a/exec/Makefile b/exec/Makefile new file mode 100644 index 0000000..a13f06c --- /dev/null +++ b/exec/Makefile @@ -0,0 +1,13 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := .. + +TEXT = make_profile xlogout +all : $(TEXT) +CLEANLIST += make_profile + +install : + $Qinstall -d $(DESTDIR)$(LIBEXECDIR) + $Qinstall -m 755 $(TEXT) $(DESTDIR)$(LIBEXECDIR) + +include $(TOPDIR)/.config/rules.mk diff --git a/exec/make_profile.in b/exec/make_profile.in new file mode 100755 index 0000000..ad28919 --- /dev/null +++ b/exec/make_profile.in @@ -0,0 +1,73 @@ +#!/bin/bash + +umask 0077 +# new profile directory +npdir=~/.config/fbpanel +# old profile directory +opdir=~/.fbpanel +# system profile directory +spdir=@datadir@ +# if profile name was not set, use 'default' +profile=${1:-default} + + +# if profile already exists do nothing +if [ -w "$npdir/$profile" ]; then + echo "Profile '$profile' already exists." + echo "$npdir/$profile" + exit 0 +fi + +# create profile +echo "Creating profile '$profile' at $npdir/$profile" +mkdir -p "$npdir" +touch "$npdir/$profile" || exit 1 + +# if personal profile with same name exists, use it +w="$w###############################################\n" +w="$w# This configuration file is not used anymore.\n" +w="$w# Now, all profiles are stored at\n# $npdir.\n" +w="$w# Edit them instead !!! \n" +w="$w###############################################\n\n" + +if [ -r "$opdir/$profile" ]; then + cp -f "$opdir/$profile" "$npdir/$profile" + echo "Using old $opdir/$profile as template" + echo -e "$w" > "/tmp/$$-$profile" + cat "$opdir/$profile" >> "/tmp/$$-$profile" + mv "/tmp/$$-$profile" "$opdir/$profile" + exit 0 +fi + +# Creates new profile using system profile as template +# $1 - system profile name +# $2 - destination profile name +function take_system_profile () +{ + [ -r "$spdir/$1" ] || return 1 + + local browser terminal filer + for browser in x-www-browser firefox opera; do + if which $browser 2> /dev/null > /dev/null; then + opt="$opt -e s/x-www-browser/$browser/" + break + fi + done + for terminal in x-terminal urxvt gnome-terminal; do + if which $terminal 2> /dev/null > /dev/null; then + opt="$opt -e s/x-terminal/$terminal/" + break + fi + done + for filer in x-file-manager thunar pcmanfm rox; do + if which $filer 2> /dev/null > /dev/null; then + opt="$opt -e s/x-file-manager/$filer/" + break + fi + done + sed $opt < "$spdir/$1" > "$npdir/$2" + echo "Using $spdir/$1 as template" +} + +take_system_profile $profile $profile || take_system_profile default $profile + diff --git a/exec/xlogout b/exec/xlogout new file mode 100755 index 0000000..fe69e42 --- /dev/null +++ b/exec/xlogout @@ -0,0 +1,49 @@ +#!/bin/bash + +# xlogout - logs user out of its X session +# Linux specific since uses /proc + +# get display name without screen number +[ -z "$DISPLAY" ] && exit 1 +DPY=${DISPLAY:1} +DPY=${DPY/.*/} +echo "DPY=${DPY}" + +# get X pid +XPID=`< /tmp/.X${DPY}-lock` +XPID=`echo $XPID` +echo "XPID=$XPID" + +# get pid of xdm (or gdm, kdm, etc). usually it's parent of X +XDMPID=`ps -o ppid --pid=$XPID | awk '{if (FNR != 1) print $1}'` +echo "XDMPID=$XDMPID" + +# recursivly find child of xdm that was started in home dir - +# it's user's session start up script +function pid_scan() +{ + + rm -f $PF + while [ $# != 0 ]; do + ps --no-headers -o pid --ppid=$1 >> $PF + shift + done + for pid in `< $PF`; do + if cwd=`ls -al /proc/$pid/cwd 2>/dev/null`; then + cwd=`sed 's/.*-> //' <<< $cwd` + [ "$cwd" == "$HOME" ] && echo $pid && return + fi + done + pids=`< $PF` + [ -n "$pids" ] && pid_scan `< $PF`; +} + +PF=/tmp/$$-pids +SPID=`pid_scan $XDMPID` +rm -f $PF + +[ -z "$SPID" ] && exit 1 +echo "Session start up script" +ps -o uid,pid,ppid,sess,cmd --pid $SPID +kill -SIGTERM -$SPID $SPID + diff --git a/fbpanel-2009.ebuild b/fbpanel-2009.ebuild new file mode 100644 index 0000000..1b564a7 --- /dev/null +++ b/fbpanel-2009.ebuild @@ -0,0 +1,27 @@ +# Copyright 1999-2005 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +inherit toolchain-funcs +inherit subversion + +ESVN_REPO_URI="https://fbpanel.svn.sourceforge.net/svnroot/fbpanel/trunk" +ESVN_PROJECT="fbpanel" + +DESCRIPTION="light-weight X11 desktop panel" +HOMEPAGE="http://fbpanel.sourceforge.net/" + +LICENSE="GPL" +SLOT="0" +KEYWORDS="~arm alpha amd64 ppc ~ppc64 x86" +IUSE="" + +RDEPEND=">=x11-libs/gtk+-2" +DEPEND="${RDEPEND} + dev-util/pkgconfig" + + +src_install() { + emake DESTDIR="${D}" install || die "Install failed" + dodoc CHANGELOG CREDITS README +} + diff --git a/fbpanel.ebuild b/fbpanel.ebuild new file mode 100644 index 0000000..f1a380b --- /dev/null +++ b/fbpanel.ebuild @@ -0,0 +1,25 @@ +# Copyright 1999-2009 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +#inherit toolchain-funcs eutils +inherit eutils + +DESCRIPTION="light-weight X11 desktop panel" +HOMEPAGE="http://fbpanel.sourceforge.net/" +SRC_URI="mirror://sourceforge/${PN}/${P}.tbz2" + +LICENSE="GPL" +SLOT="0" +KEYWORDS="~arm alpha amd64 ppc ~ppc64 x86" +IUSE="" + +RDEPEND=">=x11-libs/gtk+-2" +DEPEND="${RDEPEND} + dev-util/pkgconfig" + + +src_install() { + emake DESTDIR="${D}" install || die "Install failed" + dodoc CHANGELOG CREDITS README +} + diff --git a/panel/Makefile b/panel/Makefile new file mode 100644 index 0000000..f3dcc8f --- /dev/null +++ b/panel/Makefile @@ -0,0 +1,21 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := .. + +fbpanel_src = bg.c \ + ev.c \ + gconf.c \ + gconf_panel.c \ + gconf_plugins.c \ + gtkbar.c \ + gtkbgbox.c \ + misc.c \ + panel.c \ + plugin.c \ + run.c \ + xconf.c +fbpanel_cflags = $(GTK2_CFLAGS) $(GMODULE2_CFLAGS) $(X11_CFLAGS) +fbpanel_libs = $(GTK2_LIBS) $(GMODULE2_LIBS) $(X11_LIBS) -lm +fbpanel_type = bin + +include $(TOPDIR)/.config/rules.mk diff --git a/panel/bg.c b/panel/bg.c new file mode 100644 index 0000000..70b7bbf --- /dev/null +++ b/panel/bg.c @@ -0,0 +1,325 @@ +/* + * fb-background-monitor.c: + * + * Copyright (C) 2001, 2002 Ian McKellar + * 2002 Sun Microsystems, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * Ian McKellar + * Mark McLoughlin + */ + +#include +#include +#include +#include +#include +#include + +#include "bg.h" +#include "panel.h" + +//#define DEBUGPRN +#include "dbg.h" + + +enum { + CHANGED, + LAST_SIGNAL +}; + + +struct _FbBgClass { + GObjectClass parent_class; + void (*changed) (FbBg *monitor); +}; + +struct _FbBg { + GObject parent_instance; + + Window xroot; + Atom id; + GC gc; + Display *dpy; + Pixmap pixmap; +}; + +static void fb_bg_class_init (FbBgClass *klass); +static void fb_bg_init (FbBg *monitor); +static void fb_bg_finalize (GObject *object); +static void fb_bg_changed(FbBg *monitor); +static Pixmap fb_bg_get_xrootpmap_real(FbBg *bg); + +static guint signals [LAST_SIGNAL] = { 0 }; + +static FbBg *default_bg = NULL; + +GType +fb_bg_get_type (void) +{ + static GType object_type = 0; + + if (!object_type) { + static const GTypeInfo object_info = { + sizeof (FbBgClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) fb_bg_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (FbBg), + 0, /* n_preallocs */ + (GInstanceInitFunc) fb_bg_init, + }; + + object_type = g_type_register_static ( + G_TYPE_OBJECT, "FbBg", &object_info, 0); + } + + return object_type; +} + + + +static void +fb_bg_class_init (FbBgClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + ENTER; + signals [CHANGED] = + g_signal_new ("changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (FbBgClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + klass->changed = fb_bg_changed; + object_class->finalize = fb_bg_finalize; + RET(); +} + +static void +fb_bg_init (FbBg *bg) +{ + XGCValues gcv; + uint mask; + + ENTER; + bg->dpy = GDK_DISPLAY(); + bg->xroot = DefaultRootWindow(bg->dpy); + bg->id = XInternAtom(bg->dpy, "_XROOTPMAP_ID", False); + bg->pixmap = fb_bg_get_xrootpmap_real(bg); + gcv.ts_x_origin = 0; + gcv.ts_y_origin = 0; + gcv.fill_style = FillTiled; + mask = GCTileStipXOrigin | GCTileStipYOrigin | GCFillStyle; + if (bg->pixmap != None) { + gcv.tile = bg->pixmap; + mask |= GCTile ; + } + bg->gc = XCreateGC (bg->dpy, bg->xroot, mask, &gcv) ; + RET(); +} + + +FbBg * +fb_bg_new() +{ + ENTER; + RET(g_object_new (FB_TYPE_BG, NULL)); +} + +static void +fb_bg_finalize (GObject *object) +{ + FbBg *bg; + + ENTER; + bg = FB_BG (object); + XFreeGC(bg->dpy, bg->gc); + default_bg = NULL; + + RET(); +} + +Pixmap +fb_bg_get_xrootpmap(FbBg *bg) +{ + ENTER; + RET(bg->pixmap); +} + +static Pixmap +fb_bg_get_xrootpmap_real(FbBg *bg) +{ + Pixmap ret = None; + + ENTER; + if (bg->id) + { + int act_format, c = 2 ; + u_long nitems ; + u_long bytes_after ; + u_char *prop = NULL; + Atom ret_type; + + do + { + if (XGetWindowProperty(bg->dpy, bg->xroot, bg->id, 0, 1, + False, XA_PIXMAP, &ret_type, &act_format, + &nitems, &bytes_after, &prop) == Success) + { + if (ret_type == XA_PIXMAP) + { + ret = *((Pixmap *)prop); + c = -c ; //to quit loop + } + XFree(prop); + } + } while (--c > 0); + } + RET(ret); + +} + + + +GdkPixmap * +fb_bg_get_xroot_pix_for_area(FbBg *bg, gint x, gint y, + gint width, gint height, gint depth) +{ + GdkPixmap *gbgpix; + Pixmap bgpix; + + ENTER; + if (bg->pixmap == None) + RET(NULL); + gbgpix = gdk_pixmap_new(NULL, width, height, depth); + if (!gbgpix) { + ERR("gdk_pixmap_new failed\n"); + RET(NULL); + } + bgpix = gdk_x11_drawable_get_xid(gbgpix); + XSetTSOrigin(bg->dpy, bg->gc, -x, -y) ; + XFillRectangle(bg->dpy, bgpix, bg->gc, 0, 0, width, height); + RET(gbgpix); +} + +GdkPixmap * +fb_bg_get_xroot_pix_for_win(FbBg *bg, GtkWidget *widget) +{ + Window win; + Window dummy; + Pixmap bgpix; + GdkPixmap *gbgpix; + guint width, height, border, depth; + int x, y; + + ENTER; + if (bg->pixmap == None) + RET(NULL); + + win = GDK_WINDOW_XWINDOW(widget->window); + if (!XGetGeometry(bg->dpy, win, &dummy, &x, &y, &width, &height, &border, + &depth)) { + DBG2("XGetGeometry failed\n"); + RET(NULL); + } + if (width <= 1 || height <= 1) + RET(NULL); + + XTranslateCoordinates(bg->dpy, win, bg->xroot, 0, 0, &x, &y, &dummy); + DBG("win=%lx %dx%d%+d%+d\n", win, width, height, x, y); + gbgpix = gdk_pixmap_new(NULL, width, height, depth); + if (!gbgpix) { + ERR("gdk_pixmap_new failed\n"); + RET(NULL); + } + bgpix = gdk_x11_drawable_get_xid(gbgpix); + XSetTSOrigin(bg->dpy, bg->gc, -x, -y) ; + XFillRectangle(bg->dpy, bgpix, bg->gc, 0, 0, width, height); + RET(gbgpix); +} + +void +fb_bg_composite(GdkDrawable *base, GdkGC *gc, guint32 tintcolor, gint alpha) +{ + GdkPixbuf *ret, *ret2; + int w, h; + static GdkColormap *cmap = NULL; + + ENTER; + gdk_drawable_get_size (base, &w, &h); + if (!cmap) { + cmap = gdk_colormap_get_system (); + } + DBG("here\n"); + ret = gdk_pixbuf_get_from_drawable (NULL, base, cmap, 0, 0, 0, 0, w, h); + if (!ret) + RET(); + DBG("here w=%d h=%d\n", w, h); + ret2 = gdk_pixbuf_composite_color_simple(ret, w, h, + GDK_INTERP_HYPER, 255-alpha, MIN(w, h), tintcolor, tintcolor); + DBG("here\n"); + if (!ret2) { + g_object_unref(ret); + RET(); + } + //gdk_pixbuf_render_to_drawable (ret2, base, gc, 0, 0, 0, 0, w, h, GDK_RGB_DITHER_NONE, 0, 0); + gdk_draw_pixbuf (base, gc, ret2, 0, 0, 0, 0, w, h, + GDK_RGB_DITHER_NONE, 0, 0); + g_object_unref(ret); + g_object_unref(ret2); + RET(); +} + + +static void +fb_bg_changed(FbBg *bg) +{ + ENTER; + bg->pixmap = fb_bg_get_xrootpmap_real(bg); + if (bg->pixmap != None) { + XGCValues gcv; + + gcv.tile = bg->pixmap; + XChangeGC(bg->dpy, bg->gc, GCTile, &gcv); + DBG("changed\n"); + } + RET(); +} + + +void fb_bg_notify_changed_bg(FbBg *bg) +{ + ENTER; + g_signal_emit (bg, signals [CHANGED], 0); + RET(); +} + +FbBg *fb_bg_get_for_display(void) +{ + ENTER; + if (!default_bg) + default_bg = fb_bg_new(); + else + g_object_ref(default_bg); + RET(default_bg); +} + diff --git a/panel/bg.h b/panel/bg.h new file mode 100644 index 0000000..c7bad03 --- /dev/null +++ b/panel/bg.h @@ -0,0 +1,67 @@ +/* + * fb-background-monitor.h: + * + * Copyright (C) 2001, 2002 Ian McKellar + * 2002 Sun Microsystems, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * Ian McKellar + * Mark McLoughlin + */ + +#ifndef __FB_BG_H__ +#define __FB_BG_H__ + +/* FIXME: this needs to be made multiscreen aware + * panel_bg_get should take + * a GdkScreen argument. + */ + +#include +#include +#include +#include + +#define FB_TYPE_BG (fb_bg_get_type ()) +#define FB_BG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), \ + FB_TYPE_BG, \ + FbBg)) +#define FB_BG_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), \ + FB_TYPE_BG, \ + FbBgClass)) +#define FB_IS_BG(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), \ + FB_TYPE_BG)) +#define FB_IS_BG_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), \ + FB_TYPE_BG)) +#define FB_BG_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), \ + FB_TYPE_BG, \ + FbBgClass)) + +typedef struct _FbBgClass FbBgClass; +typedef struct _FbBg FbBg; + +GType fb_bg_get_type (void); +FbBg *fb_bg_new(void); +void fb_bg_composite(GdkDrawable *base, GdkGC *gc, guint32 tintcolor, gint alpha); +GdkPixmap *fb_bg_get_xroot_pix_for_win(FbBg *bg, GtkWidget *widget); +GdkPixmap *fb_bg_get_xroot_pix_for_area(FbBg *bg,gint x, gint y, + gint width, gint height, gint depth); +Pixmap fb_bg_get_xrootpmap(FbBg *bg); +void fb_bg_notify_changed_bg(FbBg *bg); +FbBg *fb_bg_get_for_display(void); +#endif /* __FB_BG_H__ */ diff --git a/panel/ev.c b/panel/ev.c new file mode 100644 index 0000000..a2a04cf --- /dev/null +++ b/panel/ev.c @@ -0,0 +1,302 @@ +/* + * fb-background-monitor.c: + * + * Copyright (C) 2001, 2002 Ian McKellar + * 2002 Sun Microsystems, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * Ian McKellar + * Mark McLoughlin + */ + +#include +#include +#include +#include +#include +#include + +#include "ev.h" +#include "misc.h" + +//#define DEBUGPRN +#include "dbg.h" + + + +struct _FbEvClass { + GObjectClass parent_class; + void *dummy; + void (*current_desktop)(FbEv *ev, gpointer p); + void (*active_window)(FbEv *ev, gpointer p); + void (*number_of_desktops)(FbEv *ev, gpointer p); + void (*desktop_names)(FbEv *ev, gpointer p); + void (*client_list)(FbEv *ev, gpointer p); + void (*client_list_stacking)(FbEv *ev, gpointer p); +}; + +struct _FbEv { + GObject parent_instance; + + int current_desktop; + int number_of_desktops; + char **desktop_names; + Window active_window; + Window *client_list; + Window *client_list_stacking; + + Window xroot; + Atom id; + GC gc; + Display *dpy; + Pixmap pixmap; +}; + +static void fb_ev_class_init (FbEvClass *klass); +static void fb_ev_init (FbEv *monitor); +static void fb_ev_finalize (GObject *object); + +static void ev_current_desktop(FbEv *ev, gpointer p); +static void ev_active_window(FbEv *ev, gpointer p); +static void ev_number_of_desktops(FbEv *ev, gpointer p); +static void ev_desktop_names(FbEv *ev, gpointer p); +static void ev_client_list(FbEv *ev, gpointer p); +static void ev_client_list_stacking(FbEv *ev, gpointer p); + +static guint signals [EV_LAST_SIGNAL] = { 0 }; + + +GType +fb_ev_get_type (void) +{ + static GType object_type = 0; + + if (!object_type) { + static const GTypeInfo object_info = { + sizeof (FbEvClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) fb_ev_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (FbEv), + 0, /* n_preallocs */ + (GInstanceInitFunc) fb_ev_init, + }; + + object_type = g_type_register_static ( + G_TYPE_OBJECT, "FbEv", &object_info, 0); + } + + return object_type; +} + + + +static void +fb_ev_class_init (FbEvClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + signals [EV_CURRENT_DESKTOP] = + g_signal_new ("current_desktop", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (FbEvClass, current_desktop), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals [EV_NUMBER_OF_DESKTOPS] = + g_signal_new ("number_of_desktops", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (FbEvClass, number_of_desktops), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals [EV_DESKTOP_NAMES] = + g_signal_new ("desktop_names", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (FbEvClass, desktop_names), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals [EV_ACTIVE_WINDOW] = + g_signal_new ("active_window", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (FbEvClass, active_window), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals [EV_CLIENT_LIST_STACKING] = + g_signal_new ("client_list_stacking", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (FbEvClass, client_list_stacking), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals [EV_CLIENT_LIST] = + g_signal_new ("client_list", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (FbEvClass, client_list), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + object_class->finalize = fb_ev_finalize; + + klass->current_desktop = ev_current_desktop; + klass->active_window = ev_active_window; + klass->number_of_desktops = ev_number_of_desktops; + klass->desktop_names = ev_desktop_names; + klass->client_list = ev_client_list; + klass->client_list_stacking = ev_client_list_stacking; +} + +static void +fb_ev_init (FbEv *ev) +{ + ev->number_of_desktops = -1; + ev->current_desktop = -1; + ev->active_window = None; + ev->client_list_stacking = NULL; + ev->client_list = NULL; +} + + +FbEv * +fb_ev_new() +{ + return g_object_new (FB_TYPE_EV, NULL); +} + +static void +fb_ev_finalize (GObject *object) +{ + FbEv *ev; + + ev = FB_EV (object); + //XFreeGC(ev->dpy, ev->gc); +} + +void +fb_ev_trigger(FbEv *ev, int signal) +{ + DBG("signal=%d\n", signal); + g_assert(signal >=0 && signal < EV_LAST_SIGNAL); + DBG("\n"); + g_signal_emit(ev, signals [signal], 0); +} + +static void +ev_current_desktop(FbEv *ev, gpointer p) +{ + ENTER; + ev->current_desktop = -1; + RET(); +} + +static void +ev_active_window(FbEv *ev, gpointer p) +{ + ENTER; + ev->active_window = None; + RET(); +} + +static void +ev_number_of_desktops(FbEv *ev, gpointer p) +{ + ENTER; + ev->number_of_desktops = -1; + RET(); +} + +static void +ev_desktop_names(FbEv *ev, gpointer p) +{ + ENTER; + if (ev->desktop_names) { + g_strfreev (ev->desktop_names); + ev->desktop_names = NULL; + } + RET(); +} +static void +ev_client_list(FbEv *ev, gpointer p) +{ + ENTER; + if (ev->client_list) { + XFree(ev->client_list); + ev->client_list = NULL; + } + RET(); +} + +static void +ev_client_list_stacking(FbEv *ev, gpointer p) +{ + ENTER; + if (ev->client_list_stacking) { + XFree(ev->client_list_stacking); + ev->client_list_stacking = NULL; + } + RET(); +} + +int +fb_ev_current_desktop(FbEv *ev) +{ + ENTER; + if (ev->current_desktop == -1) { + guint *data; + + data = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, XA_CARDINAL, 0); + if (data) { + ev->current_desktop = *data; + XFree (data); + } else + ev->current_desktop = 0; + } + RET(ev->current_desktop); +} + +int +fb_ev_number_of_desktops(FbEv *ev) +{ + ENTER; + if (ev->number_of_desktops == -1) { + guint *data; + + data = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_NUMBER_OF_DESKTOPS, XA_CARDINAL, 0); + if (data) { + ev->number_of_desktops = *data; + XFree (data); + } else + ev->number_of_desktops = 0; + } + RET(ev->number_of_desktops); + +} + +Window fb_ev_active_window(FbEv *ev); +Window *fb_ev_client_list(FbEv *ev); +Window *fb_ev_client_list_stacking(FbEv *ev); diff --git a/panel/ev.h b/panel/ev.h new file mode 100644 index 0000000..d6e6cc4 --- /dev/null +++ b/panel/ev.h @@ -0,0 +1,79 @@ +/* + * fb-background-monitor.h: + * + * Copyright (C) 2001, 2002 Ian McKellar + * 2002 Sun Microsystems, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * Ian McKellar + * Mark McLoughlin + */ + +#ifndef __FB_EV_H__ +#define __FB_EV_H__ + +/* FIXME: this needs to be made multiscreen aware + * panel_bg_get should take + * a GdkScreen argument. + */ + +#include +#include +#include + +#define FB_TYPE_EV (fb_ev_get_type ()) +#define FB_EV(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), \ + FB_TYPE_EV, \ + FbEv)) +#define FB_EV_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), \ + FB_TYPE_EV, \ + FbEvClass)) +#define FB_IS_EV(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), \ + FB_TYPE_EV)) +#define FB_IS_EV_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), \ + FB_TYPE_EV)) +#define FB_EV_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), \ + FB_TYPE_EV, \ + FbEvClass)) + +typedef struct _FbEvClass FbEvClass; +typedef struct _FbEv FbEv; +enum { + EV_CURRENT_DESKTOP, + EV_NUMBER_OF_DESKTOPS, + EV_DESKTOP_NAMES, + EV_ACTIVE_WINDOW, + EV_CLIENT_LIST_STACKING, + EV_CLIENT_LIST, + EV_LAST_SIGNAL +}; + +GType fb_ev_get_type (void); +FbEv *fb_ev_new(void); +void fb_ev_notify_changed_ev(FbEv *ev); +void fb_ev_trigger(FbEv *ev, int signal); + + +int fb_ev_current_desktop(FbEv *ev); +int fb_ev_number_of_desktops(FbEv *ev); +Window fb_ev_active_window(FbEv *ev); +Window *fb_ev_client_list(FbEv *ev); +Window *fb_ev_client_list_stacking(FbEv *ev); + + +#endif /* __FB_EV_H__ */ diff --git a/panel/gconf.c b/panel/gconf.c new file mode 100644 index 0000000..a41c0e9 --- /dev/null +++ b/panel/gconf.c @@ -0,0 +1,236 @@ + +#include "gconf.h" +#include "misc.h" + +//#define DEBUGPRN +#include "dbg.h" + +#define INDENT_SIZE 20 + + +gconf_block * +gconf_block_new(GCallback cb, gpointer data, int indent) +{ + GtkWidget *w; + gconf_block *b; + + b = g_new0(gconf_block, 1); + b->cb = cb; + b->data = data; + b->main = gtk_hbox_new(FALSE, 0); + /* indent */ + if (indent > 0) + { + w = gtk_hbox_new(FALSE, 0); + gtk_widget_set_size_request(w, indent, -1); + gtk_box_pack_start(GTK_BOX(b->main), w, FALSE, FALSE, 0); + } + + /* area */ + w = gtk_vbox_new(FALSE, 2); + gtk_box_pack_start(GTK_BOX(b->main), w, FALSE, FALSE, 0); + b->area = w; + + b->sgr = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + return b; +} + +void +gconf_block_free(gconf_block *b) +{ + g_object_unref(b->sgr); + g_slist_free(b->rows); + g_free(b); +} + +void +gconf_block_add(gconf_block *b, GtkWidget *w, gboolean new_row) +{ + GtkWidget *hbox; + + if (!b->rows || new_row) + { + GtkWidget *s; + + new_row = TRUE; + hbox = gtk_hbox_new(FALSE, 8); + b->rows = g_slist_prepend(b->rows, hbox); + gtk_box_pack_start(GTK_BOX(b->area), hbox, FALSE, FALSE, 0); + /* space */ + s = gtk_vbox_new(FALSE, 0); + gtk_box_pack_end(GTK_BOX(hbox), s, TRUE, TRUE, 0); + + /* allign first elem */ + if (GTK_IS_MISC(w)) + { + DBG("misc \n"); + gtk_misc_set_alignment(GTK_MISC(w), 0, 0.5); + gtk_size_group_add_widget(b->sgr, w); + } + } + else + hbox = b->rows->data; + gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 0); +} + +/********************************************************* + * Edit int + *********************************************************/ +static void +gconf_edit_int_cb(GtkSpinButton *w, xconf *xc) +{ + int i; + + i = (int) gtk_spin_button_get_value(w); + xconf_set_int(xc, i); +} + +GtkWidget * +gconf_edit_int(gconf_block *b, xconf *xc, int min, int max) +{ + gint i = 0; + GtkWidget *w; + + xconf_get_int(xc, &i); + xconf_set_int(xc, i); + w = gtk_spin_button_new_with_range((gdouble) min, (gdouble) max, 1); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(w), (gdouble) i); + g_signal_connect(G_OBJECT(w), "value-changed", + G_CALLBACK(gconf_edit_int_cb), xc); + if (b && b->cb) + { + g_signal_connect_swapped(G_OBJECT(w), "value-changed", + G_CALLBACK(b->cb), b); + } + return w; +} + +/********************************************************* + * Edit enum + *********************************************************/ +static void +gconf_edit_enum_cb(GtkComboBox *w, xconf *xc) +{ + int i; + + i = gtk_combo_box_get_active(w); + DBG("%s=%d\n", xc->name, i); + xconf_set_enum(xc, i, g_object_get_data(G_OBJECT(w), "enum")); +} + +GtkWidget * +gconf_edit_enum(gconf_block *b, xconf *xc, xconf_enum *e) +{ + gint i = 0; + GtkWidget *w; + + xconf_get_enum(xc, &i, e); + xconf_set_enum(xc, i, e); + w = gtk_combo_box_new_text(); + g_object_set_data(G_OBJECT(w), "enum", e); + while (e && e->str) + { + gtk_combo_box_insert_text(GTK_COMBO_BOX(w), e->num, + e->desc ? _(e->desc) : _(e->str)); + e++; + } + gtk_combo_box_set_active(GTK_COMBO_BOX(w), i); + g_signal_connect(G_OBJECT(w), "changed", + G_CALLBACK(gconf_edit_enum_cb), xc); + if (b && b->cb) + { + g_signal_connect_swapped(G_OBJECT(w), "changed", + G_CALLBACK(b->cb), b); + } + + return w; +} + +/********************************************************* + * Edit boolean + *********************************************************/ +static void +gconf_edit_bool_cb(GtkToggleButton *w, xconf *xc) +{ + int i; + + i = gtk_toggle_button_get_active(w); + DBG("%s=%d\n", xc->name, i); + xconf_set_enum(xc, i, bool_enum); +} + +GtkWidget * +gconf_edit_boolean(gconf_block *b, xconf *xc, gchar *text) +{ + gint i = 0; + GtkWidget *w; + + xconf_get_enum(xc, &i, bool_enum); + xconf_set_enum(xc, i, bool_enum); + w = gtk_check_button_new_with_label(text); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), i); + + g_signal_connect(G_OBJECT(w), "toggled", + G_CALLBACK(gconf_edit_bool_cb), xc); + if (b && b->cb) + { + g_signal_connect_swapped(G_OBJECT(w), "toggled", + G_CALLBACK(b->cb), b); + } + + return w; +} + + +/********************************************************* + * Edit color + *********************************************************/ +static void +gconf_edit_color_cb(GtkColorButton *w, xconf *xc) +{ + GdkColor c; + xconf *xc_alpha; + + gtk_color_button_get_color(GTK_COLOR_BUTTON(w), &c); + xconf_set_value(xc, gdk_color_to_RRGGBB(&c)); + if ((xc_alpha = g_object_get_data(G_OBJECT(w), "alpha"))) + { + guint16 a = gtk_color_button_get_alpha(GTK_COLOR_BUTTON(w)); + a >>= 8; + xconf_set_int(xc_alpha, (int) a); + } +} + +GtkWidget * +gconf_edit_color(gconf_block *b, xconf *xc_color, xconf *xc_alpha) +{ + + GtkWidget *w; + GdkColor c; + + gdk_color_parse(xconf_get_value(xc_color), &c); + + w = gtk_color_button_new(); + gtk_color_button_set_color(GTK_COLOR_BUTTON(w), &c); + if (xc_alpha) + { + gint a; + + xconf_get_int(xc_alpha, &a); + a <<= 8; /* scale to 0..FFFF from 0..FF */ + gtk_color_button_set_alpha(GTK_COLOR_BUTTON(w), (guint16) a); + g_object_set_data(G_OBJECT(w), "alpha", xc_alpha); + } + gtk_color_button_set_use_alpha(GTK_COLOR_BUTTON(w), + xc_alpha != NULL); + + g_signal_connect(G_OBJECT(w), "color-set", + G_CALLBACK(gconf_edit_color_cb), xc_color); + if (b && b->cb) + { + g_signal_connect_swapped(G_OBJECT(w), "color-set", + G_CALLBACK(b->cb), b); + } + + return w; +} diff --git a/panel/gconf.h b/panel/gconf.h new file mode 100644 index 0000000..d5bbc5f --- /dev/null +++ b/panel/gconf.h @@ -0,0 +1,27 @@ + +#ifndef _GCONF_H_ +#define _GCONF_H_ + +#include +#include "panel.h" + +typedef struct +{ + GtkWidget *main, *area; + GCallback cb; + gpointer data; + GSList *rows; + GtkSizeGroup *sgr; +} gconf_block; + + +gconf_block *gconf_block_new(GCallback cb, gpointer data, int indent); +void gconf_block_free(gconf_block *b); +void gconf_block_add(gconf_block *b, GtkWidget *w, gboolean new_row); + +GtkWidget *gconf_edit_int(gconf_block *b, xconf *xc, int min, int max); +GtkWidget *gconf_edit_enum(gconf_block *b, xconf *xc, xconf_enum *e); +GtkWidget *gconf_edit_boolean(gconf_block *b, xconf *xc, gchar *text); +GtkWidget *gconf_edit_color(gconf_block *b, xconf *xc_color, xconf *xc_alpha); + +#endif diff --git a/panel/gconf_panel.c b/panel/gconf_panel.c new file mode 100644 index 0000000..a88e60a --- /dev/null +++ b/panel/gconf_panel.c @@ -0,0 +1,415 @@ + +#include "gconf.h" +#include "panel.h" + +//#define DEBUGPRN +#include "dbg.h" + +static GtkWidget *dialog; +static GtkWidget *width_spin, *width_opt; +static GtkWidget *margin_spin; +static GtkWidget *allign_opt; + +static gconf_block *gl_block; +static gconf_block *geom_block; +static gconf_block *prop_block; +static gconf_block *effects_block; +static gconf_block *color_block; +static gconf_block *corner_block; +static gconf_block *layer_block; +static gconf_block *ah_block; + +#define INDENT_2 25 + + +GtkWidget *mk_tab_plugins(xconf *xc); + +/********************************************************* + * panel effects + *********************************************************/ +static void +effects_changed(gconf_block *b) +{ + int i; + + ENTER; + XCG(b->data, "transparent", &i, enum, bool_enum); + gtk_widget_set_sensitive(color_block->main, i); + XCG(b->data, "roundcorners", &i, enum, bool_enum); + gtk_widget_set_sensitive(corner_block->main, i); + XCG(b->data, "autohide", &i, enum, bool_enum); + gtk_widget_set_sensitive(ah_block->main, i); + + RET(); +} + + +static void +mk_effects_block(xconf *xc) +{ + GtkWidget *w; + + ENTER; + + /* label */ + w = gtk_label_new(NULL); + gtk_misc_set_alignment(GTK_MISC(w), 0, 0.5); + gtk_label_set_markup(GTK_LABEL(w), _("Visual Effects")); + gconf_block_add(gl_block, w, TRUE); + + /* effects */ + effects_block = gconf_block_new((GCallback)effects_changed, xc, 10); + + /* transparency */ + w = gconf_edit_boolean(effects_block, xconf_get(xc, "transparent"), + _("Transparency")); + gconf_block_add(effects_block, w, TRUE); + + color_block = gconf_block_new(NULL, NULL, INDENT_2); + w = gtk_label_new(_("Color settings")); + gconf_block_add(color_block, w, TRUE); + w = gconf_edit_color(color_block, xconf_get(xc, "tintcolor"), + xconf_get(xc, "alpha")); + gconf_block_add(color_block, w, FALSE); + + gconf_block_add(effects_block, color_block->main, TRUE); + + /* round corners */ + w = gconf_edit_boolean(effects_block, xconf_get(xc, "roundcorners"), + _("Round corners")); + gconf_block_add(effects_block, w, TRUE); + + corner_block = gconf_block_new(NULL, NULL, INDENT_2); + w = gtk_label_new(_("Radius is ")); + gconf_block_add(corner_block, w, TRUE); + w = gconf_edit_int(geom_block, xconf_get(xc, "roundcornersradius"), 0, 30); + gconf_block_add(corner_block, w, FALSE); + w = gtk_label_new(_("pixels")); + gconf_block_add(corner_block, w, FALSE); + gconf_block_add(effects_block, corner_block->main, TRUE); + + /* auto hide */ + w = gconf_edit_boolean(effects_block, xconf_get(xc, "autohide"), + _("Autohide")); + gconf_block_add(effects_block, w, TRUE); + + ah_block = gconf_block_new(NULL, NULL, INDENT_2); + w = gtk_label_new(_("Height when hidden is ")); + gconf_block_add(ah_block, w, TRUE); + w = gconf_edit_int(ah_block, xconf_get(xc, "heightwhenhidden"), 0, 10); + gconf_block_add(ah_block, w, FALSE); + w = gtk_label_new(_("pixels")); + gconf_block_add(ah_block, w, FALSE); + gconf_block_add(effects_block, ah_block->main, TRUE); + + w = gconf_edit_int(effects_block, xconf_get(xc, "maxelemheight"), 0, 128); + gconf_block_add(effects_block, gtk_label_new(_("Max Element Height")), TRUE); + gconf_block_add(effects_block, w, FALSE); + gconf_block_add(gl_block, effects_block->main, TRUE); + + /* empty row */ + gconf_block_add(gl_block, gtk_label_new(" "), TRUE); +} + +/********************************************************* + * panel properties + *********************************************************/ +static void +prop_changed(gconf_block *b) +{ + int i = 0; + + ENTER; + XCG(b->data, "setlayer", &i, enum, bool_enum); + gtk_widget_set_sensitive(layer_block->main, i); + RET(); +} + +static void +mk_prop_block(xconf *xc) +{ + GtkWidget *w; + + ENTER; + + /* label */ + w = gtk_label_new(NULL); + gtk_misc_set_alignment(GTK_MISC(w), 0, 0.5); + gtk_label_set_markup(GTK_LABEL(w), _("Properties")); + gconf_block_add(gl_block, w, TRUE); + + /* properties */ + prop_block = gconf_block_new((GCallback)prop_changed, xc, 10); + + /* strut */ + w = gconf_edit_boolean(prop_block, xconf_get(xc, "setpartialstrut"), + _("Do not cover by maximized windows")); + gconf_block_add(prop_block, w, TRUE); + + w = gconf_edit_boolean(prop_block, xconf_get(xc, "setdocktype"), + _("Set 'Dock' type")); + gconf_block_add(prop_block, w, TRUE); + + /* set layer */ + w = gconf_edit_boolean(prop_block, xconf_get(xc, "setlayer"), + _("Set stacking layer")); + gconf_block_add(prop_block, w, TRUE); + + layer_block = gconf_block_new(NULL, NULL, INDENT_2); + w = gtk_label_new(_("Panel is ")); + gconf_block_add(layer_block, w, TRUE); + w = gconf_edit_enum(layer_block, xconf_get(xc, "layer"), + layer_enum); + gconf_block_add(layer_block, w, FALSE); + w = gtk_label_new(_("all windows")); + gconf_block_add(layer_block, w, FALSE); + gconf_block_add(prop_block, layer_block->main, TRUE); + + + gconf_block_add(gl_block, prop_block->main, TRUE); + + /* empty row */ + gconf_block_add(gl_block, gtk_label_new(" "), TRUE); +} + +/********************************************************* + * panel geometry + *********************************************************/ +static void +geom_changed(gconf_block *b) +{ + int i, j; + + ENTER; + i = gtk_combo_box_get_active(GTK_COMBO_BOX(allign_opt)); + gtk_widget_set_sensitive(margin_spin, (i != ALLIGN_CENTER)); + i = gtk_combo_box_get_active(GTK_COMBO_BOX(width_opt)); + gtk_widget_set_sensitive(width_spin, (i != WIDTH_REQUEST)); + if (i == WIDTH_PERCENT) + gtk_spin_button_set_range(GTK_SPIN_BUTTON(width_spin), 0, 100); + else if (i == WIDTH_PIXEL) { + XCG(b->data, "edge", &j, enum, edge_enum); + gtk_spin_button_set_range(GTK_SPIN_BUTTON(width_spin), 0, + (j == EDGE_RIGHT || j == EDGE_LEFT) + ? gdk_screen_height() : gdk_screen_width()); + } + RET(); +} + +static void +mk_geom_block(xconf *xc) +{ + GtkWidget *w; + + ENTER; + + /* label */ + w = gtk_label_new(NULL); + gtk_misc_set_alignment(GTK_MISC(w), 0, 0.5); + gtk_label_set_markup(GTK_LABEL(w), _("Geometry")); + gconf_block_add(gl_block, w, TRUE); + + /* geometry */ + geom_block = gconf_block_new((GCallback)geom_changed, xc, 10); + + w = gconf_edit_int(geom_block, xconf_get(xc, "width"), 0, 2300); + gconf_block_add(geom_block, gtk_label_new(_("Width")), TRUE); + gconf_block_add(geom_block, w, FALSE); + width_spin = w; + + w = gconf_edit_enum(geom_block, xconf_get(xc, "widthtype"), + widthtype_enum); + gconf_block_add(geom_block, w, FALSE); + width_opt = w; + + w = gconf_edit_int(geom_block, xconf_get(xc, "height"), 0, 300); + gconf_block_add(geom_block, gtk_label_new(_("Height")), TRUE); + gconf_block_add(geom_block, w, FALSE); + + w = gconf_edit_enum(geom_block, xconf_get(xc, "edge"), + edge_enum); + gconf_block_add(geom_block, gtk_label_new(_("Edge")), TRUE); + gconf_block_add(geom_block, w, FALSE); + + w = gconf_edit_enum(geom_block, xconf_get(xc, "allign"), + allign_enum); + gconf_block_add(geom_block, gtk_label_new(_("Allignment")), TRUE); + gconf_block_add(geom_block, w, FALSE); + allign_opt = w; + + w = gconf_edit_int(geom_block, xconf_get(xc, "margin"), 0, 300); + gconf_block_add(geom_block, gtk_label_new(_("Margin")), FALSE); + gconf_block_add(geom_block, w, FALSE); + margin_spin = w; + + gconf_block_add(gl_block, geom_block->main, TRUE); + + /* empty row */ + gconf_block_add(gl_block, gtk_label_new(" "), TRUE); +} + +static GtkWidget * +mk_tab_global(xconf *xc) +{ + GtkWidget *page; + + ENTER; + page = gtk_vbox_new(FALSE, 1); + gtk_container_set_border_width(GTK_CONTAINER(page), 10); + gl_block = gconf_block_new(NULL, NULL, 0); + gtk_box_pack_start(GTK_BOX(page), gl_block->main, FALSE, TRUE, 0); + + mk_geom_block(xc); + mk_prop_block(xc); + mk_effects_block(xc); + + gtk_widget_show_all(page); + + geom_changed(geom_block); + effects_changed(effects_block); + prop_changed(prop_block); + + RET(page); +} + +static GtkWidget * +mk_tab_profile(xconf *xc) +{ + GtkWidget *page, *label; + gchar *s1; + + ENTER; + page = gtk_vbox_new(FALSE, 1); + gtk_container_set_border_width(GTK_CONTAINER(page), 10); + + s1 = g_strdup_printf(_("You're using '%s' profile, stored at\n" + "%s"), panel_get_profile(), panel_get_profile_file()); + label = gtk_label_new(NULL); + gtk_label_set_markup(GTK_LABEL(label), s1); + gtk_box_pack_start(GTK_BOX(page), label, FALSE, TRUE, 0); + g_free(s1); + + gtk_widget_show_all(page); + RET(page); +} + +static void +dialog_response_event(GtkDialog *_dialog, gint rid, xconf *xc) +{ + xconf *oxc = g_object_get_data(G_OBJECT(dialog), "oxc"); + + ENTER; + if (rid == GTK_RESPONSE_APPLY || + rid == GTK_RESPONSE_OK) + { + DBG("apply changes\n"); + if (xconf_cmp(xc, oxc)) + { + xconf_del(oxc, FALSE); + oxc = xconf_dup(xc); + g_object_set_data(G_OBJECT(dialog), "oxc", oxc); + xconf_save_to_profile(xc); + gtk_main_quit(); + } + } + if (rid == GTK_RESPONSE_DELETE_EVENT || + rid == GTK_RESPONSE_CLOSE || + rid == GTK_RESPONSE_OK) + { + gtk_widget_destroy(dialog); + dialog = NULL; + gconf_block_free(geom_block); + gconf_block_free(gl_block); + gconf_block_free(effects_block); + gconf_block_free(color_block); + gconf_block_free(corner_block); + gconf_block_free(layer_block); + gconf_block_free(prop_block); + gconf_block_free(ah_block); + xconf_del(xc, FALSE); + xconf_del(oxc, FALSE); + } + RET(); +} + +static gboolean +dialog_cancel(GtkDialog *_dialog, GdkEvent *event, xconf *xc) +{ + dialog_response_event(_dialog, GTK_RESPONSE_CLOSE, xc); + return FALSE; +} + +static GtkWidget * +mk_dialog(xconf *oxc) +{ + GtkWidget *sw, *nb, *label; + gchar *name; + xconf *xc; + + ENTER; + DBG("creating dialog\n"); + //name = g_strdup_printf("fbpanel settings: <%s> profile", cprofile); + name = g_strdup_printf("fbpanel settings: <%s> profile", + panel_get_profile()); + dialog = gtk_dialog_new_with_buttons (name, + NULL, + GTK_DIALOG_NO_SEPARATOR, //GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_APPLY, + GTK_RESPONSE_APPLY, + GTK_STOCK_OK, + GTK_RESPONSE_OK, + GTK_STOCK_CLOSE, + GTK_RESPONSE_CLOSE, + NULL); + g_free(name); + DBG("connecting sugnal to %p\n", dialog); + + xc = xconf_dup(oxc); + g_object_set_data(G_OBJECT(dialog), "oxc", xc); + xc = xconf_dup(oxc); + + g_signal_connect (G_OBJECT(dialog), "response", + (GCallback) dialog_response_event, xc); + // g_signal_connect (G_OBJECT(dialog), "destroy", + // (GCallback) dialog_cancel, xc); + g_signal_connect (G_OBJECT(dialog), "delete_event", + (GCallback) dialog_cancel, xc); + + gtk_window_set_modal(GTK_WINDOW(dialog), FALSE); + gtk_window_set_default_size(GTK_WINDOW(dialog), 400, 500); + gtk_window_set_icon_from_file(GTK_WINDOW(dialog), + IMGPREFIX "/logo.png", NULL); + + nb = gtk_notebook_new(); + gtk_notebook_set_show_border (GTK_NOTEBOOK(nb), FALSE); + gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), nb); + + sw = mk_tab_global(xconf_get(xc, "global")); + label = gtk_label_new(_("Panel")); + gtk_misc_set_padding(GTK_MISC(label), 4, 1); + gtk_notebook_append_page(GTK_NOTEBOOK(nb), sw, label); + + sw = mk_tab_plugins(xc); + label = gtk_label_new(_("Plugins")); + gtk_misc_set_padding(GTK_MISC(label), 4, 1); + gtk_notebook_append_page(GTK_NOTEBOOK(nb), sw, label); + + sw = mk_tab_profile(xc); + label = gtk_label_new(_("Profile")); + gtk_misc_set_padding(GTK_MISC(label), 4, 1); + gtk_notebook_append_page(GTK_NOTEBOOK(nb), sw, label); + + gtk_widget_show_all(dialog); + RET(dialog); +} + +void +configure(xconf *xc) +{ + ENTER; + DBG("dialog %p\n", dialog); + if (!dialog) + dialog = mk_dialog(xc); + gtk_widget_show(dialog); + RET(); +} diff --git a/panel/gconf_plugins.c b/panel/gconf_plugins.c new file mode 100644 index 0000000..a8be874 --- /dev/null +++ b/panel/gconf_plugins.c @@ -0,0 +1,123 @@ + + +#include "gconf.h" +#include "panel.h" + +//#define DEBUGPRN +#include "dbg.h" + +enum +{ + TYPE_COL, + NAME_COL, + N_COLUMNS +}; + +GtkTreeStore *store; +GtkWidget *tree; +GtkWidget *bbox; + +static void +mk_model(xconf *xc) +{ + GtkTreeIter iter; + xconf *pxc; + int i; + gchar *type; + + store = gtk_tree_store_new(N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING); + for (i = 0; (pxc = xconf_find(xc, "plugin", i)); i++) + { + XCG(pxc, "type", &type, str); + gtk_tree_store_append(store, &iter, NULL); + gtk_tree_store_set (store, &iter, + TYPE_COL, type, + NAME_COL, "Martin Heidegger", + -1); + } +} + +static void +tree_selection_changed_cb(GtkTreeSelection *selection, gpointer data) +{ + GtkTreeIter iter; + GtkTreeModel *model; + gchar *type; + gboolean sel; + + sel = gtk_tree_selection_get_selected(selection, &model, &iter); + if (sel) + { + gtk_tree_model_get(model, &iter, TYPE_COL, &type, -1); + g_print("%s\n", type); + g_free(type); + } + gtk_widget_set_sensitive(bbox, sel); +} + +static GtkWidget * +mk_view() +{ + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkTreeSelection *select; + + tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes("Type", + renderer, "text", TYPE_COL, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column); + + select = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree)); + gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE); + g_signal_connect(G_OBJECT(select), "changed", + G_CALLBACK(tree_selection_changed_cb), NULL); + return tree; +} + +GtkWidget * +mk_buttons() +{ + GtkWidget *bm, *b, *w; + + bm = gtk_hbox_new(FALSE, 3); + + w = gtk_button_new_from_stock(GTK_STOCK_ADD); + gtk_box_pack_start(GTK_BOX(bm), w, FALSE, TRUE, 0); + + b = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(bm), b, FALSE, TRUE, 0); + bbox = b; + gtk_widget_set_sensitive(bbox, FALSE); + + w = gtk_button_new_from_stock(GTK_STOCK_EDIT); + gtk_box_pack_start(GTK_BOX(b), w, FALSE, TRUE, 0); + w = gtk_button_new_from_stock(GTK_STOCK_DELETE); + gtk_box_pack_start(GTK_BOX(b), w, FALSE, TRUE, 0); + w = gtk_button_new_from_stock(GTK_STOCK_GO_DOWN); + gtk_box_pack_start(GTK_BOX(b), w, FALSE, TRUE, 0); + w = gtk_button_new_from_stock(GTK_STOCK_GO_UP); + gtk_box_pack_start(GTK_BOX(b), w, FALSE, TRUE, 0); + + return bm; +} + +GtkWidget * +mk_tab_plugins(xconf *xc) +{ + GtkWidget *page, *w; + + ENTER; + page = gtk_vbox_new(FALSE, 1); + gtk_container_set_border_width(GTK_CONTAINER(page), 10); + + mk_model(xc); + + w = mk_view(); + gtk_box_pack_start(GTK_BOX(page), w, TRUE, TRUE, 0); + w = mk_buttons(); + gtk_box_pack_start(GTK_BOX(page), w, FALSE, TRUE, 0); + + gtk_widget_show_all(page); + RET(page); +} diff --git a/panel/gtkbar.c b/panel/gtkbar.c new file mode 100644 index 0000000..8d285cc --- /dev/null +++ b/panel/gtkbar.c @@ -0,0 +1,254 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +#include "gtkbar.h" + +//#define DEBUGPRN +#include "dbg.h" + +#define MAX_CHILD_SIZE 150 + +static void gtk_bar_class_init (GtkBarClass *klass); +static void gtk_bar_size_request (GtkWidget *widget, GtkRequisition *requisition); +static void gtk_bar_size_allocate (GtkWidget *widget, GtkAllocation *allocation); +//static gint gtk_bar_expose (GtkWidget *widget, GdkEventExpose *event); +float ceilf(float x); + +static GtkBoxClass *parent_class = NULL; + +GType +gtk_bar_get_type (void) +{ + static GType bar_type = 0; + + if (!bar_type) + { + static const GTypeInfo bar_info = + { + sizeof (GtkBarClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_bar_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkBar), + 0, /* n_preallocs */ + NULL + }; + + bar_type = g_type_register_static (GTK_TYPE_BOX, "GtkBar", + &bar_info, 0); + } + + return bar_type; +} + +static void +gtk_bar_class_init (GtkBarClass *class) +{ + GtkWidgetClass *widget_class; + + parent_class = g_type_class_peek_parent (class); + widget_class = (GtkWidgetClass*) class; + + widget_class->size_request = gtk_bar_size_request; + widget_class->size_allocate = gtk_bar_size_allocate; + //widget_class->expose_event = gtk_bar_expose; + +} + + +GtkWidget* +gtk_bar_new(GtkOrientation orient, gint spacing, + gint child_height, gint child_width) +{ + GtkBar *bar; + + bar = g_object_new (GTK_TYPE_BAR, NULL); + GTK_BOX (bar)->spacing = spacing; + bar->orient = orient; + bar->child_width = MAX(1, child_width); + bar->child_height = MAX(1, child_height); + bar->dimension = 1; + return (GtkWidget *)bar; +} + +void +gtk_bar_set_dimension(GtkBar *bar, gint dimension) +{ + dimension = MAX(1, dimension); + if (bar->dimension != dimension) { + bar->dimension = MAX(1, dimension); + gtk_widget_queue_resize(GTK_WIDGET(bar)); + } +} + +gint gtk_bar_get_dimension(GtkBar *bar) +{ + return bar->dimension; +} + +static void +gtk_bar_size_request(GtkWidget *widget, GtkRequisition *requisition) +{ + GtkBox *box = GTK_BOX(widget); + GtkBar *bar = GTK_BAR(widget);; + GtkBoxChild *child; + GList *children; + gint nvis_children, rows, cols, dim; + + nvis_children = 0; + children = box->children; + while (children) { + child = children->data; + children = children->next; + + if (GTK_WIDGET_VISIBLE(child->widget)) { + GtkRequisition child_requisition; + + /* Do not remove child request !!! Label's proper layout depends + * on request running before alloc. */ + gtk_widget_size_request(child->widget, &child_requisition); + nvis_children++; + } + } + DBG("nvis_children=%d\n", nvis_children); + if (!nvis_children) { + requisition->width = 2; + requisition->height = 2; + return; + } + dim = MIN(bar->dimension, nvis_children); + if (bar->orient == GTK_ORIENTATION_HORIZONTAL) { + rows = dim; + cols = (gint) ceilf((float) nvis_children / rows); + } else { + cols = dim; + rows = (gint) ceilf((float) nvis_children / cols); + } + + requisition->width = bar->child_width * cols + + box->spacing * (cols - 1); + requisition->height = bar->child_height * rows + + box->spacing * (rows - 1); + DBG("width=%d, height=%d\n", requisition->width, requisition->height); +} + +static void +gtk_bar_size_allocate(GtkWidget *widget, GtkAllocation *allocation) +{ + GtkBox *box; + GtkBar *bar; + GtkBoxChild *child; + GList *children; + GtkAllocation child_allocation; + gint nvis_children, tmp, rows, cols, dim; + + ENTER; + DBG("a.w=%d a.h=%d\n", allocation->width, allocation->height); + box = GTK_BOX (widget); + bar = GTK_BAR (widget); + widget->allocation = *allocation; + + nvis_children = 0; + children = box->children; + while (children) { + child = children->data; + children = children->next; + + if (GTK_WIDGET_VISIBLE (child->widget)) + nvis_children += 1; + } + gtk_widget_queue_draw(widget); + dim = MIN(bar->dimension, nvis_children); + if (nvis_children == 0) + RET(); + if (bar->orient == GTK_ORIENTATION_HORIZONTAL) { + rows = dim; + cols = (gint) ceilf((float) nvis_children / rows); + } else { + cols = dim; + rows = (gint) ceilf((float) nvis_children / cols); + } + DBG("rows=%d cols=%d\n", rows, cols); + tmp = allocation->width - (cols - 1) * box->spacing; + child_allocation.width = MIN(tmp / cols, bar->child_width); + tmp = allocation->height - (rows - 1) * box->spacing; + child_allocation.height = MIN(tmp / rows, bar->child_height); + + if (child_allocation.width < 1) + child_allocation.width = 1; + if (child_allocation.height < 1) + child_allocation.height = 1; + DBG("child alloc: width=%d height=%d\n", + child_allocation.width, + child_allocation.height); + + child_allocation.x = allocation->x; + child_allocation.y = allocation->y; + children = box->children; + tmp = 0; + while (children) { + child = children->data; + children = children->next; + + if (GTK_WIDGET_VISIBLE (child->widget)) { + DBG("allocate x=%d y=%d\n", child_allocation.x, + child_allocation.y); + gtk_widget_size_allocate(child->widget, &child_allocation); + tmp++; + if (tmp == cols) { + child_allocation.x = allocation->x; + child_allocation.y += child_allocation.height + box->spacing; + tmp = 0; + } else { + child_allocation.x += child_allocation.width + box->spacing; + } + } + } + RET(); +} + + +#if 0 +static gint +gtk_bar_expose (GtkWidget *widget, GdkEventExpose *event) +{ + ENTER; + + if (GTK_WIDGET_DRAWABLE (widget)) { + int w, h; + + DBG("w, h = %d,%d\n", w, h); + if (!GTK_WIDGET_APP_PAINTABLE (widget)) + gtk_paint_flat_box (widget->style, widget->window, + widget->state, GTK_SHADOW_NONE, + NULL /*&event->area*/, widget, NULL, + 0, 0, w, h); + } + RET(FALSE); +} +#endif diff --git a/panel/gtkbar.h b/panel/gtkbar.h new file mode 100644 index 0000000..5698026 --- /dev/null +++ b/panel/gtkbar.h @@ -0,0 +1,76 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __GTK_BAR_H__ +#define __GTK_BAR_H__ + + +#include +#include + + +#ifdef __cplusplus +//extern "C" { +#endif /* __cplusplus */ + + +#define GTK_TYPE_BAR (gtk_bar_get_type ()) +#define GTK_BAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_BAR, GtkBar)) +#define GTK_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_BAR, GtkBarClass)) +#define GTK_IS_BAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_BAR)) +#define GTK_IS_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_BAR)) +#define GTK_BAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_BAR, GtkBarClass)) + + +typedef struct _GtkBar GtkBar; +typedef struct _GtkBarClass GtkBarClass; + +struct _GtkBar +{ + GtkBox box; + gint child_height, child_width; + gint dimension; + GtkOrientation orient; +}; + +struct _GtkBarClass +{ + GtkBoxClass parent_class; +}; + + +GType gtk_bar_get_type (void) G_GNUC_CONST; +GtkWidget* gtk_bar_new(GtkOrientation orient, + gint spacing, gint child_height, gint child_width); +void gtk_bar_set_dimension(GtkBar *bar, gint dimension); +gint gtk_bar_get_dimension(GtkBar *bar); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __GTK_BAR_H__ */ diff --git a/panel/gtkbgbox.c b/panel/gtkbgbox.c new file mode 100644 index 0000000..166d416 --- /dev/null +++ b/panel/gtkbgbox.c @@ -0,0 +1,404 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +#include +#include "gtkbgbox.h" +#include "bg.h" +#include +#include +#include +#include +#include +#include + + +//#define DEBUGPRN +#include "dbg.h" + +typedef struct { + GdkPixmap *pixmap; + guint32 tintcolor; + gint alpha; + int bg_type; + FbBg *bg; + gulong sid; +} GtkBgboxPrivate; + + + +#define GTK_BGBOX_GET_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE((obj), GTK_TYPE_BGBOX, GtkBgboxPrivate) + +static void gtk_bgbox_class_init (GtkBgboxClass *klass); +static void gtk_bgbox_init (GtkBgbox *bgbox); +static void gtk_bgbox_realize (GtkWidget *widget); +static void gtk_bgbox_size_request (GtkWidget *widget, GtkRequisition *requisition); +static void gtk_bgbox_size_allocate (GtkWidget *widget, GtkAllocation *allocation); +static void gtk_bgbox_style_set (GtkWidget *widget, GtkStyle *previous_style); +static gboolean gtk_bgbox_configure_event(GtkWidget *widget, GdkEventConfigure *e); +#if 0 +static gboolean gtk_bgbox_destroy_event (GtkWidget *widget, GdkEventAny *event); +static gboolean gtk_bgbox_delete_event (GtkWidget *widget, GdkEventAny *event); +#endif + +static void gtk_bgbox_finalize (GObject *object); + +static void gtk_bgbox_set_bg_root(GtkWidget *widget, GtkBgboxPrivate *priv); +static void gtk_bgbox_set_bg_inherit(GtkWidget *widget, GtkBgboxPrivate *priv); +static void gtk_bgbox_bg_changed(FbBg *bg, GtkWidget *widget); + +static GtkBinClass *parent_class = NULL; + +GType +gtk_bgbox_get_type (void) +{ + static GType bgbox_type = 0; + + if (!bgbox_type) + { + static const GTypeInfo bgbox_info = + { + sizeof (GtkBgboxClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_bgbox_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkBgbox), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_bgbox_init, + }; + + bgbox_type = g_type_register_static (GTK_TYPE_BIN, "GtkBgbox", + &bgbox_info, 0); + } + + return bgbox_type; +} + +static void +gtk_bgbox_class_init (GtkBgboxClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + parent_class = g_type_class_peek_parent (class); + + widget_class->realize = gtk_bgbox_realize; + widget_class->size_request = gtk_bgbox_size_request; + widget_class->size_allocate = gtk_bgbox_size_allocate; + widget_class->style_set = gtk_bgbox_style_set; + widget_class->configure_event = gtk_bgbox_configure_event; + //widget_class->destroy_event = gtk_bgbox_destroy_event; + //widget_class->delete_event = gtk_bgbox_delete_event; + + object_class->finalize = gtk_bgbox_finalize; + g_type_class_add_private (class, sizeof (GtkBgboxPrivate)); +} + +static void +gtk_bgbox_init (GtkBgbox *bgbox) +{ + GtkBgboxPrivate *priv; + + ENTER; + GTK_WIDGET_UNSET_FLAGS (bgbox, GTK_NO_WINDOW); + + priv = GTK_BGBOX_GET_PRIVATE (bgbox); + priv->bg_type = BG_NONE; + priv->sid = 0; + RET(); +} + +GtkWidget* +gtk_bgbox_new (void) +{ + ENTER; + RET(g_object_new (GTK_TYPE_BGBOX, NULL)); +} + +static void +gtk_bgbox_finalize (GObject *object) +{ + GtkBgboxPrivate *priv; + + ENTER; + priv = GTK_BGBOX_GET_PRIVATE(GTK_WIDGET(object)); + if (priv->pixmap) { + g_object_unref(priv->pixmap); + priv->pixmap = NULL; + } + if (priv->sid) { + g_signal_handler_disconnect(priv->bg, priv->sid); + priv->sid = 0; + } + if (priv->bg) { + g_object_unref(priv->bg); + priv->bg = NULL; + } + RET(); +} + +static GdkFilterReturn +gtk_bgbox_event_filter(GdkXEvent *xevent, GdkEvent *event, GtkWidget *widget) +{ + XEvent *ev = (XEvent *) xevent; + + ENTER; + if (ev->type == ConfigureNotify) { + gtk_widget_queue_draw(widget); + //gtk_bgbox_style_set(widget, NULL); + DBG("ConfigureNotify %d %d %d %d\n", + ev->xconfigure.x, + ev->xconfigure.y, + ev->xconfigure.width, + ev->xconfigure.height + ); + } + RET(GDK_FILTER_CONTINUE); +} + +static void +gtk_bgbox_realize (GtkWidget *widget) +{ + GdkWindowAttr attributes; + gint attributes_mask; + gint border_width; + GtkBgboxPrivate *priv; + + ENTER; + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + + border_width = GTK_CONTAINER (widget)->border_width; + + attributes.x = widget->allocation.x + border_width; + attributes.y = widget->allocation.y + border_width; + attributes.width = widget->allocation.width - 2*border_width; + attributes.height = widget->allocation.height - 2*border_width; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.event_mask = gtk_widget_get_events (widget) + | GDK_BUTTON_MOTION_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_ENTER_NOTIFY_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_EXPOSURE_MASK + | GDK_STRUCTURE_MASK; + + priv = GTK_BGBOX_GET_PRIVATE (widget); + + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.wclass = GDK_INPUT_OUTPUT; + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + gdk_window_set_user_data (widget->window, widget); + widget->style = gtk_style_attach (widget->style, widget->window); + if (priv->bg_type == BG_NONE) + gtk_bgbox_set_background(widget, BG_STYLE, 0, 0); + gdk_window_add_filter(widget->window, (GdkFilterFunc) gtk_bgbox_event_filter, widget); + RET(); +} + + +static void +gtk_bgbox_style_set (GtkWidget *widget, GtkStyle *previous_style) +{ + GtkBgboxPrivate *priv; + + ENTER; + priv = GTK_BGBOX_GET_PRIVATE (widget); + if (GTK_WIDGET_REALIZED (widget) && !GTK_WIDGET_NO_WINDOW (widget)) { + gtk_bgbox_set_background(widget, priv->bg_type, priv->tintcolor, priv->alpha); + } + RET(); +} + +/* gtk discards configure_event for GTK_WINDOW_CHILD. too pitty */ +static gboolean +gtk_bgbox_configure_event (GtkWidget *widget, GdkEventConfigure *e) +{ + ENTER; + DBG("geom: size (%d, %d). pos (%d, %d)\n", e->width, e->height, e->x, e->y); + RET(FALSE); + +} + +static void +gtk_bgbox_size_request (GtkWidget *widget, GtkRequisition *requisition) +{ + GtkBin *bin = GTK_BIN (widget); + ENTER; + requisition->width = GTK_CONTAINER (widget)->border_width * 2; + requisition->height = GTK_CONTAINER (widget)->border_width * 2; + + if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) + { + GtkRequisition child_requisition; + + gtk_widget_size_request (bin->child, &child_requisition); + + requisition->width += child_requisition.width; + requisition->height += child_requisition.height; + } + RET(); +} + +/* calls with same allocation are usually refer to exactly same background + * and we just skip them for optimization reason. + * so if you see artifacts or unupdated background - reallocate bg on every call + */ +static void +gtk_bgbox_size_allocate (GtkWidget *widget, GtkAllocation *wa) +{ + GtkBin *bin; + GtkAllocation ca; + GtkBgboxPrivate *priv; + int same_alloc, border; + + ENTER; + same_alloc = !memcmp(&widget->allocation, wa, sizeof(*wa)); + DBG("same alloc = %d\n", same_alloc); + DBG("x=%d y=%d w=%d h=%d\n", wa->x, wa->y, wa->width, wa->height); + DBG("x=%d y=%d w=%d h=%d\n", widget->allocation.x, widget->allocation.y, + widget->allocation.width, widget->allocation.height); + widget->allocation = *wa; + bin = GTK_BIN (widget); + border = GTK_CONTAINER (widget)->border_width; + ca.x = border; + ca.y = border; + ca.width = MAX (wa->width - border * 2, 0); + ca.height = MAX (wa->height - border * 2, 0); + + if (GTK_WIDGET_REALIZED (widget) && !GTK_WIDGET_NO_WINDOW (widget) + && !same_alloc) { + priv = GTK_BGBOX_GET_PRIVATE (widget); + DBG("move resize pos=%d,%d geom=%dx%d\n", wa->x, wa->y, wa->width, + wa->height); + gdk_window_move_resize (widget->window, wa->x, wa->y, wa->width, wa->height); + gtk_bgbox_set_background(widget, priv->bg_type, priv->tintcolor, priv->alpha); + } + + if (bin->child) + gtk_widget_size_allocate (bin->child, &ca); + RET(); +} + + +static void +gtk_bgbox_bg_changed(FbBg *bg, GtkWidget *widget) +{ + GtkBgboxPrivate *priv; + + ENTER; + priv = GTK_BGBOX_GET_PRIVATE (widget); + if (GTK_WIDGET_REALIZED (widget) && !GTK_WIDGET_NO_WINDOW (widget)) { + gtk_bgbox_set_background(widget, priv->bg_type, priv->tintcolor, priv->alpha); + } + RET(); +} + +void +gtk_bgbox_set_background(GtkWidget *widget, int bg_type, guint32 tintcolor, gint alpha) +{ + GtkBgboxPrivate *priv; + + ENTER; + if (!(GTK_IS_BGBOX (widget))) + RET(); + + priv = GTK_BGBOX_GET_PRIVATE (widget); + DBG("widget=%p bg_type old:%d new:%d\n", widget, priv->bg_type, bg_type); + if (priv->pixmap) { + g_object_unref(priv->pixmap); + priv->pixmap = NULL; + } + priv->bg_type = bg_type; + if (priv->bg_type == BG_STYLE) { + gtk_style_set_background(widget->style, widget->window, widget->state); + if (priv->sid) { + g_signal_handler_disconnect(priv->bg, priv->sid); + priv->sid = 0; + } + if (priv->bg) { + g_object_unref(priv->bg); + priv->bg = NULL; + } + } else { + if (!priv->bg) + priv->bg = fb_bg_get_for_display(); + if (!priv->sid) + priv->sid = g_signal_connect(G_OBJECT(priv->bg), "changed", G_CALLBACK(gtk_bgbox_bg_changed), widget); + + if (priv->bg_type == BG_ROOT) { + priv->tintcolor = tintcolor; + priv->alpha = alpha; + gtk_bgbox_set_bg_root(widget, priv); + } else if (priv->bg_type == BG_INHERIT) { + gtk_bgbox_set_bg_inherit(widget, priv); + } + } + gtk_widget_queue_draw(widget); + g_object_notify(G_OBJECT (widget), "style"); + + DBG("queue draw all %p\n", widget); + RET(); +} + +static void +gtk_bgbox_set_bg_root(GtkWidget *widget, GtkBgboxPrivate *priv) +{ + priv = GTK_BGBOX_GET_PRIVATE (widget); + + ENTER; + priv->pixmap = fb_bg_get_xroot_pix_for_win(priv->bg, widget); + if (!priv->pixmap || priv->pixmap == GDK_NO_BG) { + //priv->bg_type = BG_NONE; + priv->pixmap = NULL; + gtk_style_set_background(widget->style, widget->window, widget->state); + gtk_widget_queue_draw_area(widget, 0, 0, + widget->allocation.width, widget->allocation.height); + DBG("no root pixmap was found\n"); + RET(); + } + if (priv->alpha) + fb_bg_composite(priv->pixmap, widget->style->black_gc, + priv->tintcolor, priv->alpha); + gdk_window_set_back_pixmap(widget->window, priv->pixmap, FALSE); + RET(); +} + +static void +gtk_bgbox_set_bg_inherit(GtkWidget *widget, GtkBgboxPrivate *priv) +{ + priv = GTK_BGBOX_GET_PRIVATE (widget); + + ENTER; + gdk_window_set_back_pixmap(widget->window, NULL, TRUE); + RET(); +} diff --git a/panel/gtkbgbox.h b/panel/gtkbgbox.h new file mode 100644 index 0000000..16c2ac6 --- /dev/null +++ b/panel/gtkbgbox.h @@ -0,0 +1,72 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __GTK_BGBOX_H__ +#define __GTK_BGBOX_H__ + + +#include +#include +#include "bg.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#define GTK_TYPE_BGBOX (gtk_bgbox_get_type ()) +#define GTK_BGBOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_BGBOX, GtkBgbox)) +#define GTK_BGBOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_BGBOX, GtkBgboxClass)) +#define GTK_IS_BGBOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_BGBOX)) +#define GTK_IS_BGBOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_BGBOX)) +#define GTK_BGBOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_BGBOX, GtkBgboxClass)) + +typedef struct _GtkBgbox GtkBgbox; +typedef struct _GtkBgboxClass GtkBgboxClass; + +struct _GtkBgbox +{ + GtkBin bin; +}; + +struct _GtkBgboxClass +{ + GtkBinClass parent_class; +}; + +enum { BG_NONE, BG_STYLE, BG_ROOT, BG_INHERIT, BG_LAST }; + +GType gtk_bgbox_get_type (void) G_GNUC_CONST; +GtkWidget* gtk_bgbox_new (void); +void gtk_bgbox_set_background (GtkWidget *widget, int bg_type, guint32 tintcolor, gint alpha); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __GTK_BGBOX_H__ */ diff --git a/panel/misc.c b/panel/misc.c new file mode 100644 index 0000000..16173a8 --- /dev/null +++ b/panel/misc.c @@ -0,0 +1,1045 @@ + + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "misc.h" +#include "gtkbgbox.h" + +//#define DEBUGPRN +#include "dbg.h" + +extern panel *the_panel; + +GtkIconTheme *icon_theme; + +/* X11 data types */ +Atom a_UTF8_STRING; +Atom a_XROOTPMAP_ID; + +/* old WM spec */ +Atom a_WM_STATE; +Atom a_WM_CLASS; +Atom a_WM_DELETE_WINDOW; +Atom a_WM_PROTOCOLS; + +/* new NET spec */ +Atom a_NET_WORKAREA; +Atom a_NET_CLIENT_LIST; +Atom a_NET_CLIENT_LIST_STACKING; +Atom a_NET_NUMBER_OF_DESKTOPS; +Atom a_NET_CURRENT_DESKTOP; +Atom a_NET_DESKTOP_NAMES; +Atom a_NET_DESKTOP_GEOMETRY; +Atom a_NET_ACTIVE_WINDOW; +Atom a_NET_CLOSE_WINDOW; +Atom a_NET_SUPPORTED; +Atom a_NET_WM_DESKTOP; +Atom a_NET_WM_STATE; +Atom a_NET_WM_STATE_SKIP_TASKBAR; +Atom a_NET_WM_STATE_SKIP_PAGER; +Atom a_NET_WM_STATE_STICKY; +Atom a_NET_WM_STATE_HIDDEN; +Atom a_NET_WM_STATE_SHADED; +Atom a_NET_WM_STATE_ABOVE; +Atom a_NET_WM_STATE_BELOW; +Atom a_NET_WM_WINDOW_TYPE; +Atom a_NET_WM_WINDOW_TYPE_DESKTOP; +Atom a_NET_WM_WINDOW_TYPE_DOCK; +Atom a_NET_WM_WINDOW_TYPE_TOOLBAR; +Atom a_NET_WM_WINDOW_TYPE_MENU; +Atom a_NET_WM_WINDOW_TYPE_UTILITY; +Atom a_NET_WM_WINDOW_TYPE_SPLASH; +Atom a_NET_WM_WINDOW_TYPE_DIALOG; +Atom a_NET_WM_WINDOW_TYPE_NORMAL; +Atom a_NET_WM_DESKTOP; +Atom a_NET_WM_NAME; +Atom a_NET_WM_VISIBLE_NAME; +Atom a_NET_WM_STRUT; +Atom a_NET_WM_STRUT_PARTIAL; +Atom a_NET_WM_ICON; +Atom a_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR; + +xconf_enum allign_enum[] = { + { .num = ALLIGN_LEFT, .str = c_("left") }, + { .num = ALLIGN_RIGHT, .str = c_("right") }, + { .num = ALLIGN_CENTER, .str = c_("center")}, + { .num = 0, .str = NULL }, +}; +xconf_enum edge_enum[] = { + { .num = EDGE_LEFT, .str = c_("left") }, + { .num = EDGE_RIGHT, .str = c_("right") }, + { .num = EDGE_TOP, .str = c_("top") }, + { .num = EDGE_BOTTOM, .str = c_("bottom") }, + { .num = 0, .str = NULL }, +}; +xconf_enum widthtype_enum[] = { + { .num = WIDTH_REQUEST, .str = "request" , .desc = c_("dynamic") }, + { .num = WIDTH_PIXEL, .str = "pixel" , .desc = c_("pixels") }, + { .num = WIDTH_PERCENT, .str = "percent", .desc = c_("% of screen") }, + { .num = 0, .str = NULL }, +}; +xconf_enum heighttype_enum[] = { + { .num = HEIGHT_PIXEL, .str = c_("pixel") }, + { .num = 0, .str = NULL }, +}; +xconf_enum bool_enum[] = { + { .num = 0, .str = "false" }, + { .num = 1, .str = "true" }, + { .num = 0, .str = NULL }, +}; +xconf_enum pos_enum[] = { + { .num = POS_NONE, .str = "none" }, + { .num = POS_START, .str = "start" }, + { .num = POS_END, .str = "end" }, + { .num = 0, .str = NULL}, +}; +xconf_enum layer_enum[] = { + { .num = LAYER_ABOVE, .str = c_("above") }, + { .num = LAYER_BELOW, .str = c_("below") }, + { .num = 0, .str = NULL}, +}; + + +int +str2num(xconf_enum *p, gchar *str, int defval) +{ + ENTER; + for (;p && p->str; p++) { + if (!g_ascii_strcasecmp(str, p->str)) + RET(p->num); + } + RET(defval); +} + +gchar * +num2str(xconf_enum *p, int num, gchar *defval) +{ + ENTER; + for (;p && p->str; p++) { + if (num == p->num) + RET(p->str); + } + RET(defval); +} + + +void resolve_atoms() +{ + ENTER; + + a_UTF8_STRING = XInternAtom(GDK_DISPLAY(), "UTF8_STRING", False); + a_XROOTPMAP_ID = XInternAtom(GDK_DISPLAY(), "_XROOTPMAP_ID", False); + a_WM_STATE = XInternAtom(GDK_DISPLAY(), "WM_STATE", False); + a_WM_CLASS = XInternAtom(GDK_DISPLAY(), "WM_CLASS", False); + a_WM_DELETE_WINDOW = XInternAtom(GDK_DISPLAY(), "WM_DELETE_WINDOW", False); + a_WM_PROTOCOLS = XInternAtom(GDK_DISPLAY(), "WM_PROTOCOLS", False); + a_NET_WORKAREA = XInternAtom(GDK_DISPLAY(), "_NET_WORKAREA", False); + a_NET_CLIENT_LIST = XInternAtom(GDK_DISPLAY(), "_NET_CLIENT_LIST", False); + a_NET_CLIENT_LIST_STACKING = XInternAtom(GDK_DISPLAY(), "_NET_CLIENT_LIST_STACKING", False); + a_NET_NUMBER_OF_DESKTOPS = XInternAtom(GDK_DISPLAY(), "_NET_NUMBER_OF_DESKTOPS", False); + a_NET_CURRENT_DESKTOP = XInternAtom(GDK_DISPLAY(), "_NET_CURRENT_DESKTOP", False); + a_NET_DESKTOP_NAMES = XInternAtom(GDK_DISPLAY(), "_NET_DESKTOP_NAMES", False); + a_NET_DESKTOP_GEOMETRY = XInternAtom(GDK_DISPLAY(), "_NET_DESKTOP_GEOMETRY", False); + a_NET_ACTIVE_WINDOW = XInternAtom(GDK_DISPLAY(), "_NET_ACTIVE_WINDOW", False); + a_NET_SUPPORTED = XInternAtom(GDK_DISPLAY(), "_NET_SUPPORTED", False); + a_NET_WM_DESKTOP = XInternAtom(GDK_DISPLAY(), "_NET_WM_DESKTOP", False); + a_NET_WM_STATE = XInternAtom(GDK_DISPLAY(), "_NET_WM_STATE", False); + a_NET_WM_STATE_SKIP_TASKBAR = XInternAtom(GDK_DISPLAY(), "_NET_WM_STATE_SKIP_TASKBAR", False); + a_NET_WM_STATE_SKIP_PAGER = XInternAtom(GDK_DISPLAY(), "_NET_WM_STATE_SKIP_PAGER", False); + a_NET_WM_STATE_STICKY = XInternAtom(GDK_DISPLAY(), "_NET_WM_STATE_STICKY", False); + a_NET_WM_STATE_HIDDEN = XInternAtom(GDK_DISPLAY(), "_NET_WM_STATE_HIDDEN", False); + a_NET_WM_STATE_SHADED = XInternAtom(GDK_DISPLAY(), "_NET_WM_STATE_SHADED", False); + a_NET_WM_STATE_ABOVE = XInternAtom(GDK_DISPLAY(), "_NET_WM_STATE_ABOVE", False); + a_NET_WM_STATE_BELOW = XInternAtom(GDK_DISPLAY(), "_NET_WM_STATE_BELOW", False); + a_NET_WM_STATE_SHADED = XInternAtom(GDK_DISPLAY(), "_NET_WM_STATE_SHADED", False); + a_NET_WM_WINDOW_TYPE = XInternAtom(GDK_DISPLAY(), "_NET_WM_WINDOW_TYPE", False); + + a_NET_WM_WINDOW_TYPE_DESKTOP = XInternAtom(GDK_DISPLAY(), "_NET_WM_WINDOW_TYPE_DESKTOP", False); + a_NET_WM_WINDOW_TYPE_DOCK = XInternAtom(GDK_DISPLAY(), "_NET_WM_WINDOW_TYPE_DOCK", False); + a_NET_WM_WINDOW_TYPE_TOOLBAR = XInternAtom(GDK_DISPLAY(), "_NET_WM_WINDOW_TYPE_TOOLBAR", False); + a_NET_WM_WINDOW_TYPE_MENU = XInternAtom(GDK_DISPLAY(), "_NET_WM_WINDOW_TYPE_MENU", False); + a_NET_WM_WINDOW_TYPE_UTILITY = XInternAtom(GDK_DISPLAY(), "_NET_WM_WINDOW_TYPE_UTILITY", False); + a_NET_WM_WINDOW_TYPE_SPLASH = XInternAtom(GDK_DISPLAY(), "_NET_WM_WINDOW_TYPE_SPLASH", False); + a_NET_WM_WINDOW_TYPE_DIALOG = XInternAtom(GDK_DISPLAY(), "_NET_WM_WINDOW_TYPE_DIALOG", False); + a_NET_WM_WINDOW_TYPE_NORMAL = XInternAtom(GDK_DISPLAY(), "_NET_WM_WINDOW_TYPE_NORMAL", False); + a_NET_WM_DESKTOP = XInternAtom(GDK_DISPLAY(), "_NET_WM_DESKTOP", False); + a_NET_WM_NAME = XInternAtom(GDK_DISPLAY(), "_NET_WM_NAME", False); + a_NET_WM_VISIBLE_NAME = XInternAtom(GDK_DISPLAY(), "_NET_WM_VISIBLE_NAME", False); + a_NET_WM_STRUT = XInternAtom(GDK_DISPLAY(), "_NET_WM_STRUT", False); + a_NET_WM_STRUT_PARTIAL = XInternAtom(GDK_DISPLAY(), "_NET_WM_STRUT_PARTIAL", False); + a_NET_WM_ICON = XInternAtom(GDK_DISPLAY(), "_NET_WM_ICON", False); + a_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR + = XInternAtom(GDK_DISPLAY(), "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR", False); + + RET(); +} + + +void fb_init() +{ + resolve_atoms(); + icon_theme = gtk_icon_theme_get_default(); +} + +void fb_free() +{ + // MUST NOT be ref'd or unref'd + // g_object_unref(icon_theme); +} + +void +Xclimsg(Window win, long type, long l0, long l1, long l2, long l3, long l4) +{ + XClientMessageEvent xev; + + xev.type = ClientMessage; + xev.window = win; + xev.send_event = True; + xev.display = gdk_display; + xev.message_type = type; + xev.format = 32; + xev.data.l[0] = l0; + xev.data.l[1] = l1; + xev.data.l[2] = l2; + xev.data.l[3] = l3; + xev.data.l[4] = l4; + XSendEvent(GDK_DISPLAY(), GDK_ROOT_WINDOW(), False, + (SubstructureNotifyMask | SubstructureRedirectMask), + (XEvent *) & xev); +} + +void +Xclimsgwm(Window win, Atom type, Atom arg) +{ + XClientMessageEvent xev; + + xev.type = ClientMessage; + xev.window = win; + xev.message_type = type; + xev.format = 32; + xev.data.l[0] = arg; + xev.data.l[1] = GDK_CURRENT_TIME; + XSendEvent(GDK_DISPLAY(), win, False, 0L, (XEvent *) &xev); +} + + +void * +get_utf8_property(Window win, Atom atom) +{ + + Atom type; + int format; + gulong nitems; + gulong bytes_after; + gchar *retval; + int result; + guchar *tmp = NULL; + + type = None; + retval = NULL; + result = XGetWindowProperty (GDK_DISPLAY(), win, atom, 0, G_MAXLONG, False, + a_UTF8_STRING, &type, &format, &nitems, + &bytes_after, &tmp); + if (result != Success) + return NULL; + if (tmp) { + if (type == a_UTF8_STRING && format == 8 && nitems != 0) + retval = g_strndup ((gchar *)tmp, nitems); + XFree (tmp); + } + return retval; + +} + +char ** +get_utf8_property_list(Window win, Atom atom, int *count) +{ + Atom type; + int format, i; + gulong nitems; + gulong bytes_after; + gchar *s, **retval = NULL; + int result; + guchar *tmp = NULL; + + *count = 0; + result = XGetWindowProperty(GDK_DISPLAY(), win, atom, 0, G_MAXLONG, False, + a_UTF8_STRING, &type, &format, &nitems, + &bytes_after, &tmp); + if (result != Success || type != a_UTF8_STRING || tmp == NULL) + return NULL; + + if (nitems) { + gchar *val = (gchar *) tmp; + DBG("res=%d(%d) nitems=%d val=%s\n", result, Success, nitems, val); + for (i = 0; i < nitems; i++) { + if (!val[i]) + (*count)++; + } + retval = g_new0 (char*, *count + 2); + for (i = 0, s = val; i < *count; i++, s = s + strlen (s) + 1) { + retval[i] = g_strdup(s); + } + if (val[nitems-1]) { + result = nitems - (s - val); + DBG("val does not ends by 0, moving last %d bytes\n", result); + g_memmove(s - 1, s, result); + val[nitems-1] = 0; + DBG("s=%s\n", s -1); + retval[i] = g_strdup(s - 1); + (*count)++; + } + } + XFree (tmp); + + return retval; + +} + +void * +get_xaproperty (Window win, Atom prop, Atom type, int *nitems) +{ + Atom type_ret; + int format_ret; + unsigned long items_ret; + unsigned long after_ret; + unsigned char *prop_data; + + ENTER; + prop_data = NULL; + if (XGetWindowProperty (GDK_DISPLAY(), win, prop, 0, 0x7fffffff, False, + type, &type_ret, &format_ret, &items_ret, + &after_ret, &prop_data) != Success) + RET(NULL); + DBG("win=%x prop=%d type=%d rtype=%d rformat=%d nitems=%d\n", win, prop, + type, type_ret, format_ret, items_ret); + + if (nitems) + *nitems = items_ret; + RET(prop_data); +} + +static char* +text_property_to_utf8 (const XTextProperty *prop) +{ + char **list; + int count; + char *retval; + + ENTER; + list = NULL; + count = gdk_text_property_to_utf8_list (gdk_x11_xatom_to_atom (prop->encoding), + prop->format, + prop->value, + prop->nitems, + &list); + + DBG("count=%d\n", count); + if (count == 0) + return NULL; + + retval = list[0]; + list[0] = g_strdup (""); /* something to free */ + + g_strfreev (list); + + RET(retval); +} + +char * +get_textproperty(Window win, Atom atom) +{ + XTextProperty text_prop; + char *retval; + + ENTER; + if (XGetTextProperty(GDK_DISPLAY(), win, &text_prop, atom)) { + DBG("format=%d enc=%d nitems=%d value=%s \n", + text_prop.format, + text_prop.encoding, + text_prop.nitems, + text_prop.value); + retval = text_property_to_utf8 (&text_prop); + if (text_prop.nitems > 0) + XFree (text_prop.value); + RET(retval); + + } + RET(NULL); +} + + +guint +get_net_number_of_desktops() +{ + guint desknum; + guint32 *data; + + ENTER; + data = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_NUMBER_OF_DESKTOPS, + XA_CARDINAL, 0); + if (!data) + RET(0); + + desknum = *data; + XFree (data); + RET(desknum); +} + + +guint +get_net_current_desktop () +{ + guint desk; + guint32 *data; + + ENTER; + data = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, XA_CARDINAL, 0); + if (!data) + RET(0); + + desk = *data; + XFree (data); + RET(desk); +} + +guint +get_net_wm_desktop(Window win) +{ + guint desk = 0; + guint *data; + + ENTER; + data = get_xaproperty (win, a_NET_WM_DESKTOP, XA_CARDINAL, 0); + if (data) { + desk = *data; + XFree (data); + } else + DBG("can't get desktop num for win 0x%lx", win); + RET(desk); +} + +void +get_net_wm_state(Window win, net_wm_state *nws) +{ + Atom *state; + int num3; + + + ENTER; + bzero(nws, sizeof(*nws)); + if (!(state = get_xaproperty(win, a_NET_WM_STATE, XA_ATOM, &num3))) + RET(); + + DBG( "%x: netwm state = { ", (unsigned int)win); + while (--num3 >= 0) { + if (state[num3] == a_NET_WM_STATE_SKIP_PAGER) { + DBGE("NET_WM_STATE_SKIP_PAGER "); + nws->skip_pager = 1; + } else if (state[num3] == a_NET_WM_STATE_SKIP_TASKBAR) { + DBGE("NET_WM_STATE_SKIP_TASKBAR "); + nws->skip_taskbar = 1; + } else if (state[num3] == a_NET_WM_STATE_STICKY) { + DBGE("NET_WM_STATE_STICKY "); + nws->sticky = 1; + } else if (state[num3] == a_NET_WM_STATE_HIDDEN) { + DBGE("NET_WM_STATE_HIDDEN "); + nws->hidden = 1; + } else if (state[num3] == a_NET_WM_STATE_SHADED) { + DBGE("NET_WM_STATE_SHADED "); + nws->shaded = 1; + } else { + DBGE("... "); + } + } + XFree(state); + DBGE( "}\n"); + RET(); +} + + + + +void +get_net_wm_window_type(Window win, net_wm_window_type *nwwt) +{ + Atom *state; + int num3; + + + ENTER; + bzero(nwwt, sizeof(*nwwt)); + if (!(state = get_xaproperty(win, a_NET_WM_WINDOW_TYPE, XA_ATOM, &num3))) + RET(); + + DBG( "%x: netwm state = { ", (unsigned int)win); + while (--num3 >= 0) { + if (state[num3] == a_NET_WM_WINDOW_TYPE_DESKTOP) { + DBG("NET_WM_WINDOW_TYPE_DESKTOP "); + nwwt->desktop = 1; + } else if (state[num3] == a_NET_WM_WINDOW_TYPE_DOCK) { + DBG( "NET_WM_WINDOW_TYPE_DOCK "); + nwwt->dock = 1; + } else if (state[num3] == a_NET_WM_WINDOW_TYPE_TOOLBAR) { + DBG( "NET_WM_WINDOW_TYPE_TOOLBAR "); + nwwt->toolbar = 1; + } else if (state[num3] == a_NET_WM_WINDOW_TYPE_MENU) { + DBG( "NET_WM_WINDOW_TYPE_MENU "); + nwwt->menu = 1; + } else if (state[num3] == a_NET_WM_WINDOW_TYPE_UTILITY) { + DBG( "NET_WM_WINDOW_TYPE_UTILITY "); + nwwt->utility = 1; + } else if (state[num3] == a_NET_WM_WINDOW_TYPE_SPLASH) { + DBG( "NET_WM_WINDOW_TYPE_SPLASH "); + nwwt->splash = 1; + } else if (state[num3] == a_NET_WM_WINDOW_TYPE_DIALOG) { + DBG( "NET_WM_WINDOW_TYPE_DIALOG "); + nwwt->dialog = 1; + } else if (state[num3] == a_NET_WM_WINDOW_TYPE_NORMAL) { + DBG( "NET_WM_WINDOW_TYPE_NORMAL "); + nwwt->normal = 1; + } else { + DBG( "... "); + } + } + XFree(state); + DBG( "}\n"); + RET(); +} + + + + +static void +calculate_width(int scrw, int wtype, int allign, int margin, + int *panw, int *x) +{ + ENTER; + DBG("scrw=%d\n", scrw); + DBG("IN panw=%d\n", *panw); + //scrw -= 2; + if (wtype == WIDTH_PERCENT) { + /* sanity check */ + if (*panw > 100) + *panw = 100; + else if (*panw < 0) + *panw = 1; + *panw = ((gfloat) scrw * (gfloat) *panw) / 100.0; + } + if (*panw > scrw) + *panw = scrw; + + if (allign != ALLIGN_CENTER) { + if (margin > scrw) { + ERR( "margin is bigger then edge size %d > %d. Ignoring margin\n", + margin, scrw); + margin = 0; + } + if (wtype == WIDTH_PERCENT) + //*panw = MAX(scrw - margin, *panw); + ; + else + *panw = MIN(scrw - margin, *panw); + } + DBG("OUT panw=%d\n", *panw); + if (allign == ALLIGN_LEFT) + *x += margin; + else if (allign == ALLIGN_RIGHT) { + *x += scrw - *panw - margin; + if (*x < 0) + *x = 0; + } else if (allign == ALLIGN_CENTER) + *x += (scrw - *panw) / 2; + RET(); +} + + +void +calculate_position(panel *np) +{ + int sswidth, ssheight, minx, miny; + + ENTER; + if (0) { + //if (np->curdesk < np->wa_len/4) { + minx = np->workarea[np->curdesk*4 + 0]; + miny = np->workarea[np->curdesk*4 + 1]; + sswidth = np->workarea[np->curdesk*4 + 2]; + ssheight = np->workarea[np->curdesk*4 + 3]; + } else { + minx = miny = 0; + sswidth = gdk_screen_width(); + ssheight = gdk_screen_height(); + + } + + if (np->edge == EDGE_TOP || np->edge == EDGE_BOTTOM) { + np->aw = np->width; + np->ax = minx; + calculate_width(sswidth, np->widthtype, np->allign, np->margin, + &np->aw, &np->ax); + np->ah = np->height; + np->ah = MIN(PANEL_HEIGHT_MAX, np->ah); + np->ah = MAX(PANEL_HEIGHT_MIN, np->ah); + np->ay = miny + ((np->edge == EDGE_TOP) ? 0 : (ssheight - np->ah)); + + } else { + np->ah = np->width; + np->ay = miny; + calculate_width(ssheight, np->widthtype, np->allign, np->margin, + &np->ah, &np->ay); + np->aw = np->height; + np->aw = MIN(PANEL_HEIGHT_MAX, np->aw); + np->aw = MAX(PANEL_HEIGHT_MIN, np->aw); + np->ax = minx + ((np->edge == EDGE_LEFT) ? 0 : (sswidth - np->aw)); + } + if (!np->aw) + np->aw = 1; + if (!np->ah) + np->ah = 1; + + /* + if (!np->visible) { + DBG("pushing of screen dx=%d dy=%d\n", np->ah_dx, np->ah_dy); + np->ax += np->ah_dx; + np->ay += np->ah_dy; + } + */ + DBG("x=%d y=%d w=%d h=%d\n", np->ax, np->ay, np->aw, np->ah); + RET(); +} + + + +gchar * +expand_tilda(gchar *file) +{ + ENTER; + if (!file) + RET(NULL); + RET((file[0] == '~') ? + g_strdup_printf("%s%s", getenv("HOME"), file+1) + : g_strdup(file)); + +} + + +void +get_button_spacing(GtkRequisition *req, GtkContainer *parent, gchar *name) +{ + GtkWidget *b; + //gint focus_width; + //gint focus_pad; + + ENTER; + b = gtk_button_new(); + gtk_widget_set_name(GTK_WIDGET(b), name); + GTK_WIDGET_UNSET_FLAGS (b, GTK_CAN_FOCUS); + GTK_WIDGET_UNSET_FLAGS (b, GTK_CAN_DEFAULT); + gtk_container_set_border_width (GTK_CONTAINER (b), 0); + + if (parent) + gtk_container_add(parent, b); + + gtk_widget_show(b); + gtk_widget_size_request(b, req); + + gtk_widget_destroy(b); + RET(); +} + + +guint32 +gcolor2rgb24(GdkColor *color) +{ + guint32 i; + guint16 r, g, b; + + ENTER; + + r = color->red * 0xFF / 0xFFFF; + g = color->green * 0xFF / 0xFFFF; + b = color->blue * 0xFF / 0xFFFF; + DBG("%x %x %x ==> %x %x %x\n", color->red, color->green, color->blue, r, g, b); + + i = (color->red * 0xFF / 0xFFFF) & 0xFF; + i <<= 8; + i |= (color->green * 0xFF / 0xFFFF) & 0xFF; + i <<= 8; + i |= (color->blue * 0xFF / 0xFFFF) & 0xFF; + DBG("i=%x\n", i); + RET(i); +} + +gchar * +gdk_color_to_RRGGBB(GdkColor *color) +{ + static gchar str[10]; // #RRGGBB + \0 + g_sprintf(str, "#%02x%02x%02x", + color->red >> 8, color->green >> 8, color->blue >> 8); + return str; +} + +void +menu_pos(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *widget) +{ + int ox, oy, w, h; + + ENTER; + if (widget) { + gdk_window_get_origin(widget->window, &ox, &oy); + ox += widget->allocation.x; + oy += widget->allocation.y; + } else { + gdk_display_get_pointer(gdk_display_get_default(), NULL, &ox, &oy, NULL); + ox -= 20; + if (ox < 0) + ox = 0; + oy -= 10; + if (oy < 0) + oy = 0; + } + w = GTK_WIDGET(menu)->requisition.width; + h = GTK_WIDGET(menu)->requisition.height; + if (the_panel->orientation == GTK_ORIENTATION_HORIZONTAL) { + // x + *x = ox; + if (*x + w > gdk_screen_width()) + *x = gdk_screen_width() - w; + // y + if (the_panel->edge == EDGE_TOP) + *y = the_panel->ah; + else + *y = gdk_screen_height() - the_panel->ah - h; + } else { + // x + if (the_panel->edge == EDGE_LEFT) + *x = the_panel->aw; + else + *x = gdk_screen_width() - the_panel->aw - w; + // y + *y = oy; + if (*y + h > gdk_screen_height()) + *y = gdk_screen_height() - h; + } + DBG("w-h %d %d\n", w, h); + *push_in = TRUE; + RET(); +} + +gchar * +indent(int level) +{ + static gchar *space[] = { + "", + " ", + " ", + " ", + " ", + }; + + if (level > sizeof(space)) + level = sizeof(space); + RET(space[level]); +} + + + + +/********************************************************************** + * FB Pixbuf * + **********************************************************************/ + +#define MAX_SIZE 192 + +/* Creates a pixbuf. Several sources are tried in these order: + * icon named @iname + * file from @fname + * icon named "missing-image" as a fallabck, if @use_fallback is TRUE. + * Returns pixbuf or NULL on failure + * + * Result pixbuf is always smaller then MAX_SIZE + */ +GdkPixbuf * +fb_pixbuf_new(gchar *iname, gchar *fname, int width, int height, + gboolean use_fallback) +{ + GdkPixbuf *pb = NULL; + int size; + + ENTER; + size = MIN(192, MAX(width, height)); + if (iname && !pb) + pb = gtk_icon_theme_load_icon(icon_theme, iname, size, + GTK_ICON_LOOKUP_FORCE_SIZE, NULL); + if (fname && !pb) + pb = gdk_pixbuf_new_from_file_at_size(fname, width, height, NULL); + if (use_fallback && !pb) + pb = gtk_icon_theme_load_icon(icon_theme, "gtk-missing-image", size, + GTK_ICON_LOOKUP_FORCE_SIZE, NULL); + RET(pb); +} + +/* Creates hilighted version of front image to reflect mouse enter + */ +static GdkPixbuf * +fb_pixbuf_make_back_image(GdkPixbuf *front, gulong hicolor) +{ + GdkPixbuf *back; + guchar *src, *up, extra[3]; + int i; + + ENTER; + back = gdk_pixbuf_add_alpha(front, FALSE, 0, 0, 0); + if (!back) { + g_object_ref(G_OBJECT(front)); + RET(front); + } + src = gdk_pixbuf_get_pixels (back); + for (i = 2; i >= 0; i--, hicolor >>= 8) + extra[i] = hicolor & 0xFF; + for (up = src + gdk_pixbuf_get_height(back) * gdk_pixbuf_get_rowstride (back); + src < up; src+=4) { + if (src[3] == 0) + continue; + for (i = 0; i < 3; i++) { + if (src[i] + extra[i] >= 255) + src[i] = 255; + else + src[i] += extra[i]; + } + } + RET(back); +} + +#define PRESS_GAP 2 +static GdkPixbuf * +fb_pixbuf_make_press_image(GdkPixbuf *front) +{ + GdkPixbuf *press, *tmp; + int w, h; + + ENTER; + w = gdk_pixbuf_get_width(front) - 2 * PRESS_GAP; + h = gdk_pixbuf_get_height(front) - 2 * PRESS_GAP; + press = gdk_pixbuf_copy(front); + tmp = gdk_pixbuf_scale_simple(front, w, h, GDK_INTERP_HYPER); + if (press && tmp) { + gdk_pixbuf_fill(press, 0); + gdk_pixbuf_copy_area(tmp, + 0, 0, // src_x, src_y + w, h, // width, height + press, // dest_pixbuf + PRESS_GAP, PRESS_GAP); // dst_x, dst_y + g_object_unref(G_OBJECT(tmp)); + RET(press); + } + if (press) + g_object_unref(G_OBJECT(press)); + if (tmp) + g_object_unref(G_OBJECT(tmp)); + + g_object_ref(G_OBJECT(front)); + RET(front); +} + +/********************************************************************** + * FB Image * + **********************************************************************/ + +#define PIXBBUF_NUM 3 +typedef struct { + gchar *iname, *fname; + int width, height; + gulong itc_id; /* icon theme change callback id */ + gulong hicolor; + int i; /* pixbuf index */ + GdkPixbuf *pix[PIXBBUF_NUM]; +} fb_image_conf_t; + +static void fb_image_free(GObject *image); +static void fb_image_icon_theme_changed(GtkIconTheme *icon_theme, + GtkWidget *image); + +/* Creates an image widget from fb_pixbuf and updates it on every icon theme + * change. To keep its internal state, image allocates some data and frees it + * in "destroy" callback + */ +GtkWidget * +fb_image_new(gchar *iname, gchar *fname, int width, int height) +{ + GtkWidget *image; + fb_image_conf_t *conf; + + image = gtk_image_new(); + conf = g_new0(fb_image_conf_t, 1); /* exits if fails */ + g_object_set_data(G_OBJECT(image), "conf", conf); + conf->itc_id = g_signal_connect_after (G_OBJECT(icon_theme), + "changed", (GCallback) fb_image_icon_theme_changed, image); + g_signal_connect (G_OBJECT(image), + "destroy", (GCallback) fb_image_free, NULL); + conf->iname = g_strdup(iname); + conf->fname = g_strdup(fname); + conf->width = width; + conf->height = height; + conf->pix[0] = fb_pixbuf_new(iname, fname, width, height, TRUE); + gtk_image_set_from_pixbuf(GTK_IMAGE(image), conf->pix[0]); + gtk_widget_show(image); + RET(image); +} + + +/* Frees image's resources + */ +static void +fb_image_free(GObject *image) +{ + fb_image_conf_t *conf; + int i; + + ENTER; + conf = g_object_get_data(image, "conf"); + g_signal_handler_disconnect(G_OBJECT(icon_theme), conf->itc_id); + g_free(conf->iname); + g_free(conf->fname); + for (i = 0; i < PIXBBUF_NUM; i++) + if (conf->pix[i]) + g_object_unref(G_OBJECT(conf->pix[i])); + g_free(conf); + RET(); +} + +/* Reloads image's pixbuf upon icon theme change + */ +static void +fb_image_icon_theme_changed(GtkIconTheme *icon_theme, GtkWidget *image) +{ + fb_image_conf_t *conf; + int i; + + ENTER; + conf = g_object_get_data(G_OBJECT(image), "conf"); + DBG("%s / %s\n", conf->iname, conf->fname); + for (i = 0; i < PIXBBUF_NUM; i++) + if (conf->pix[i]) { + g_object_unref(G_OBJECT(conf->pix[i])); + conf->pix[i] = NULL; + } + conf->pix[0] = fb_pixbuf_new(conf->iname, conf->fname, + conf->width, conf->height, TRUE); + conf->pix[1] = fb_pixbuf_make_back_image(conf->pix[0], conf->hicolor); + conf->pix[2] = fb_pixbuf_make_press_image(conf->pix[1]); + gtk_image_set_from_pixbuf(GTK_IMAGE(image), conf->pix[0]); + RET(); +} + + +/********************************************************************** + * FB Button * + **********************************************************************/ + +static gboolean fb_button_cross(GtkImage *widget, GdkEventCrossing *event); +static gboolean fb_button_pressed(GtkWidget *widget, GdkEventButton *event); + +/* Creates fb_button - bgbox with fb_image. bgbox provides pseudo transparent + * background and event capture. fb_image follows icon theme change. + * Additionaly, fb_button highlightes an image on mouse enter and runs simple + * animation when clicked. + * FIXME: @label parameter is currently ignored + */ +GtkWidget * +fb_button_new(gchar *iname, gchar *fname, int width, int height, + gulong hicolor, gchar *label) +{ + GtkWidget *b, *image; + fb_image_conf_t *conf; + + ENTER; + b = gtk_bgbox_new(); + gtk_container_set_border_width(GTK_CONTAINER(b), 0); + GTK_WIDGET_UNSET_FLAGS (b, GTK_CAN_FOCUS); + image = fb_image_new(iname, fname, width, height); + gtk_misc_set_alignment(GTK_MISC(image), 0.5, 0.5); + gtk_misc_set_padding (GTK_MISC(image), 0, 0); + conf = g_object_get_data(G_OBJECT(image), "conf"); + conf->hicolor = hicolor; + conf->pix[1] = fb_pixbuf_make_back_image(conf->pix[0], conf->hicolor); + conf->pix[2] = fb_pixbuf_make_press_image(conf->pix[1]); + gtk_widget_add_events(b, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); + g_signal_connect_swapped (G_OBJECT (b), "enter-notify-event", + G_CALLBACK (fb_button_cross), image); + g_signal_connect_swapped (G_OBJECT (b), "leave-notify-event", + G_CALLBACK (fb_button_cross), image); + g_signal_connect_swapped (G_OBJECT (b), "button-release-event", + G_CALLBACK (fb_button_pressed), image); + g_signal_connect_swapped (G_OBJECT (b), "button-press-event", + G_CALLBACK (fb_button_pressed), image); + gtk_container_add(GTK_CONTAINER(b), image); + gtk_widget_show_all(b); + RET(b); +} + + +/* Flips front and back images upon mouse cross event - GDK_ENTER_NOTIFY + * or GDK_LEAVE_NOTIFY + */ +static gboolean +fb_button_cross(GtkImage *widget, GdkEventCrossing *event) +{ + fb_image_conf_t *conf; + int i; + + ENTER; + conf = g_object_get_data(G_OBJECT(widget), "conf"); + if (event->type == GDK_LEAVE_NOTIFY) { + i = 0; + } else { + i = 1; + } + if (conf->i != i) { + conf->i = i; + gtk_image_set_from_pixbuf(GTK_IMAGE(widget), conf->pix[i]); + } + DBG("%s/%s - %s - pix[%d]=%p\n", conf->iname, conf->fname, + (event->type == GDK_LEAVE_NOTIFY) ? "out" : "in", + conf->i, conf->pix[conf->i]); + RET(TRUE); +} + +static gboolean +fb_button_pressed(GtkWidget *widget, GdkEventButton *event) +{ + fb_image_conf_t *conf; + int i; + + ENTER; + conf = g_object_get_data(G_OBJECT(widget), "conf"); + if (event->type == GDK_BUTTON_PRESS) { + i = 2; + } else { + if ((event->x >=0 && event->x < widget->allocation.width) + && (event->y >=0 && event->y < widget->allocation.height)) + i = 1; + else + i = 0; + } + if (conf->i != i) { + conf->i = i; + gtk_image_set_from_pixbuf(GTK_IMAGE(widget), conf->pix[i]); + } + RET(FALSE); +} + + diff --git a/panel/misc.h b/panel/misc.h new file mode 100644 index 0000000..e603fa4 --- /dev/null +++ b/panel/misc.h @@ -0,0 +1,53 @@ +#ifndef MISC_H +#define MISC_H + +#include +#include +#include +#include +#include + +#include "panel.h" + +int str2num(xconf_enum *p, gchar *str, int defval); +gchar *num2str(xconf_enum *p, int num, gchar *defval); + + +void Xclimsg(Window win, long type, long l0, long l1, long l2, long l3, long l4); +void Xclimsgwm(Window win, Atom type, Atom arg); +void *get_xaproperty (Window win, Atom prop, Atom type, int *nitems); +char *get_textproperty(Window win, Atom prop); +void *get_utf8_property(Window win, Atom atom); +char **get_utf8_property_list(Window win, Atom atom, int *count); + +void fb_init(void); +void fb_free(void); +//Window Select_Window(Display *dpy); +guint get_net_number_of_desktops(); +guint get_net_current_desktop (); +guint get_net_wm_desktop(Window win); +void get_net_wm_state(Window win, net_wm_state *nws); +void get_net_wm_window_type(Window win, net_wm_window_type *nwwt); + +void calculate_position(panel *np); +gchar *expand_tilda(gchar *file); + +void get_button_spacing(GtkRequisition *req, GtkContainer *parent, gchar *name); +guint32 gcolor2rgb24(GdkColor *color); +gchar *gdk_color_to_RRGGBB(GdkColor *color); + +GdkPixbuf *fb_pixbuf_new(gchar *iname, gchar *fname, int width, int height, + gboolean use_fallback); +GtkWidget *fb_image_new(gchar *iname, gchar *fname, int width, int height); +GtkWidget *fb_button_new(gchar *iname, gchar *fname, int width, int height, + gulong hicolor, gchar *name); + + +void menu_pos(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *widget); + +void configure(); +gchar *indent(int level); + +FILE *get_profile_file(gchar *profile, char *perm); + +#endif diff --git a/panel/panel.c b/panel/panel.c new file mode 100644 index 0000000..10470e2 --- /dev/null +++ b/panel/panel.c @@ -0,0 +1,952 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "plugin.h" +#include "panel.h" +#include "misc.h" +#include "bg.h" +#include "gtkbgbox.h" + + +static gchar version[] = PROJECT_VERSION; +static gchar *profile = "default"; +static gchar *profile_file; + +guint mwid; // mouse watcher thread id +guint hpid; // hide panel thread id + + +FbEv *fbev; +gint force_quit = 0; +int config; + +//#define DEBUGPRN +#include "dbg.h" + +/** verbosity level of dbg and log functions */ +int log_level = LOG_WARN; + +static panel *p; +panel *the_panel; + +void +panel_set_wm_strut(panel *p) +{ + gulong data[12] = { 0 }; + int i = 4; + + ENTER; + if (!GTK_WIDGET_MAPPED(p->topgwin)) + return; + /* most wm's tend to ignore struts of unmapped windows, and that's how + * fbpanel hides itself. so no reason to set it. */ + if (p->autohide) + return; + switch (p->edge) { + case EDGE_LEFT: + i = 0; + data[i] = p->aw; + data[4 + i*2] = p->ay; + data[5 + i*2] = p->ay + p->ah; + if (p->autohide) data[i] = p->height_when_hidden; + break; + case EDGE_RIGHT: + i = 1; + data[i] = p->aw; + data[4 + i*2] = p->ay; + data[5 + i*2] = p->ay + p->ah; + if (p->autohide) data[i] = p->height_when_hidden; + break; + case EDGE_TOP: + i = 2; + data[i] = p->ah; + data[4 + i*2] = p->ax; + data[5 + i*2] = p->ax + p->aw; + if (p->autohide) data[i] = p->height_when_hidden; + break; + case EDGE_BOTTOM: + i = 3; + data[i] = p->ah; + data[4 + i*2] = p->ax; + data[5 + i*2] = p->ax + p->aw; + if (p->autohide) data[i] = p->height_when_hidden; + break; + default: + ERR("wrong edge %d. strut won't be set\n", p->edge); + RET(); + } + DBG("type %d. width %ld. from %ld to %ld\n", i, data[i], data[4 + i*2], + data[5 + i*2]); + + /* if wm supports STRUT_PARTIAL it will ignore STRUT */ + XChangeProperty(GDK_DISPLAY(), p->topxwin, a_NET_WM_STRUT_PARTIAL, + XA_CARDINAL, 32, PropModeReplace, (unsigned char *) data, 12); + /* old spec, for wms that do not support STRUT_PARTIAL */ + XChangeProperty(GDK_DISPLAY(), p->topxwin, a_NET_WM_STRUT, + XA_CARDINAL, 32, PropModeReplace, (unsigned char *) data, 4); + + RET(); +} +#if 0 +static void +print_wmdata(panel *p) +{ + int i; + + ENTER; + RET(); + DBG("desktop %d/%d\n", p->curdesk, p->desknum); + DBG("workarea\n"); + for (i = 0; i < p->wa_len/4; i++) + DBG("(%d, %d) x (%d, %d)\n", + p->workarea[4*i + 0], + p->workarea[4*i + 1], + p->workarea[4*i + 2], + p->workarea[4*i + 3]); + RET(); +} +#endif + +static GdkFilterReturn +panel_event_filter(GdkXEvent *xevent, GdkEvent *event, panel *p) +{ + Atom at; + Window win; + XEvent *ev = (XEvent *) xevent; + + ENTER; + DBG("win = 0x%lx\n", ev->xproperty.window); + if (ev->type != PropertyNotify ) + RET(GDK_FILTER_CONTINUE); + + at = ev->xproperty.atom; + win = ev->xproperty.window; + DBG("win=%lx at=%ld\n", win, at); + if (win == GDK_ROOT_WINDOW()) { + if (at == a_NET_CLIENT_LIST) { + DBG("A_NET_CLIENT_LIST\n"); + fb_ev_trigger(fbev, EV_CLIENT_LIST); + } else if (at == a_NET_CURRENT_DESKTOP) { + DBG("A_NET_CURRENT_DESKTOP\n"); + p->curdesk = get_net_current_desktop(); + fb_ev_trigger(fbev, EV_CURRENT_DESKTOP); + } else if (at == a_NET_NUMBER_OF_DESKTOPS) { + DBG("A_NET_NUMBER_OF_DESKTOPS\n"); + p->desknum = get_net_number_of_desktops(); + fb_ev_trigger(fbev, EV_NUMBER_OF_DESKTOPS); + } else if (at == a_NET_DESKTOP_NAMES) { + DBG("A_NET_DESKTOP_NAMES\n"); + fb_ev_trigger(fbev, EV_DESKTOP_NAMES); + } else if (at == a_NET_ACTIVE_WINDOW) { + DBG("A_NET_ACTIVE_WINDOW\n"); + fb_ev_trigger(fbev, EV_ACTIVE_WINDOW); + }else if (at == a_NET_CLIENT_LIST_STACKING) { + DBG("A_NET_CLIENT_LIST_STACKING\n"); + fb_ev_trigger(fbev, EV_CLIENT_LIST_STACKING); + } else if (at == a_NET_WORKAREA) { + DBG("A_NET_WORKAREA\n"); + //p->workarea = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_WORKAREA, + // XA_CARDINAL, &p->wa_len); + //print_wmdata(p); + } else if (at == a_XROOTPMAP_ID) { + if (p->transparent) + fb_bg_notify_changed_bg(p->bg); + } else if (at == a_NET_DESKTOP_GEOMETRY) { + DBG("a_NET_DESKTOP_GEOMETRY\n"); + gtk_main_quit(); + } else + RET(GDK_FILTER_CONTINUE); + RET(GDK_FILTER_REMOVE); + } + DBG("non root %lx\n", win); + RET(GDK_FILTER_CONTINUE); +} + +/**************************************************** + * panel's handlers for GTK events * + ****************************************************/ + +static gint +panel_destroy_event(GtkWidget * widget, GdkEvent * event, gpointer data) +{ + ENTER; + gtk_main_quit(); + force_quit = 1; + RET(FALSE); +} + +static void +panel_size_req(GtkWidget *widget, GtkRequisition *req, panel *p) +{ + ENTER; + DBG("IN req=(%d, %d)\n", req->width, req->height); + if (p->widthtype == WIDTH_REQUEST) + p->width = (p->orientation == GTK_ORIENTATION_HORIZONTAL) ? req->width : req->height; + if (p->heighttype == HEIGHT_REQUEST) + p->height = (p->orientation == GTK_ORIENTATION_HORIZONTAL) ? req->height : req->width; + calculate_position(p); + req->width = p->aw; + req->height = p->ah; + DBG("OUT req=(%d, %d)\n", req->width, req->height); + RET(); +} + +static void +make_round_corners(panel *p) +{ + GdkBitmap *b; + GdkGC* gc; + GdkColor black = { 0, 0, 0, 0}; + GdkColor white = { 1, 0xffff, 0xffff, 0xffff}; + int w, h, r, br; + + ENTER; + w = p->aw; + h = p->ah; + r = p->round_corners_radius; + if (2*r > MIN(w, h)) { + r = MIN(w, h) / 2; + DBG("chaning radius to %d\n", r); + } + b = gdk_pixmap_new(NULL, w, h, 1); + gc = gdk_gc_new(GDK_DRAWABLE(b)); + gdk_gc_set_foreground(gc, &black); + gdk_draw_rectangle(GDK_DRAWABLE(b), gc, TRUE, 0, 0, w, h); + gdk_gc_set_foreground(gc, &white); + gdk_draw_rectangle(GDK_DRAWABLE(b), gc, TRUE, r, 0, w-2*r, h); + gdk_draw_rectangle(GDK_DRAWABLE(b), gc, TRUE, 0, r, r, h-2*r); + gdk_draw_rectangle(GDK_DRAWABLE(b), gc, TRUE, w-r, r, r, h-2*r); + + br = 2 * r; + gdk_draw_arc(GDK_DRAWABLE(b), gc, TRUE, 0, 0, br, br, 0*64, 360*64); + gdk_draw_arc(GDK_DRAWABLE(b), gc, TRUE, 0, h-br-1, br, br, 0*64, 360*64); + gdk_draw_arc(GDK_DRAWABLE(b), gc, TRUE, w-br, 0, br, br, 0*64, 360*64); + gdk_draw_arc(GDK_DRAWABLE(b), gc, TRUE, w-br, h-br-1, br, br, 0*64, 360*64); + + gtk_widget_shape_combine_mask(p->topgwin, b, 0, 0); + g_object_unref(gc); + g_object_unref(b); + + RET(); +} + +static gboolean +panel_configure_event(GtkWidget *widget, GdkEventConfigure *e, panel *p) +{ + ENTER; + DBG("cur geom: %dx%d+%d+%d\n", e->width, e->height, e->x, e->y); + DBG("req geom: %dx%d+%d+%d\n", p->aw, p->ah, p->ax, p->ay); + if (e->width == p->cw && e->height == p->ch && e->x == p->cx && e->y == + p->cy) { + DBG("dup. exiting\n"); + RET(FALSE); + } + /* save current geometry */ + p->cw = e->width; + p->ch = e->height; + p->cx = e->x; + p->cy = e->y; + + /* if panel size is not we have requested, just wait, it will */ + if (e->width != p->aw || e->height != p->ah) { + DBG("size_req not yet ready. exiting\n"); + RET(FALSE); + } + + /* if panel wasn't at requested position, then send another request */ + if (e->x != p->ax || e->y != p->ay) { + DBG("move %d,%d\n", p->ax, p->ay); + gtk_window_move(GTK_WINDOW(widget), p->ax, p->ay); + RET(FALSE); + } + + /* panel is at right place, lets go on */ + if (p->transparent) { + fb_bg_notify_changed_bg(p->bg); + DBG("remake bg image\n"); + } + if (p->setstrut) { + panel_set_wm_strut(p); + DBG("set_wm_strut\n"); + } + if (p->round_corners) { + make_round_corners(p); + DBG("make_round_corners\n"); + } + gtk_widget_show(p->topgwin); + if (p->setstrut) { + panel_set_wm_strut(p); + DBG("set_wm_strut\n"); + } + RET(FALSE); + +} + +/**************************************************** + * autohide * + ****************************************************/ + +/* Autohide is behaviour when panel hides itself when mouse is "far enough" + * and pops up again when mouse comes "close enough". + * Formally, it's a state machine with 3 states that driven by mouse + * coordinates and timer: + * 1. VISIBLE - ensures that panel is visible. When/if mouse goes "far enough" + * switches to WAITING state + * 2. WAITING - starts timer. If mouse comes "close enough", stops timer and + * switches to VISIBLE. If timer expires, switches to HIDDEN + * 3. HIDDEN - hides panel. When mouse comes "close enough" switches to VISIBLE + * + * Note 1 + * Mouse coordinates are queried every PERIOD milisec + * + * Note 2 + * If mouse is less then GAP pixels to panel it's considered to be close, + * otherwise it's far + */ + +#define GAP 2 +#define PERIOD 300 + +static gboolean ah_state_visible(panel *p); +static gboolean ah_state_waiting(panel *p); +static gboolean ah_state_hidden(panel *p); + +static gboolean +panel_mapped(GtkWidget *widget, GdkEvent *event, panel *p) +{ + ENTER; + if (p->autohide) { + ah_stop(p); + ah_start(p); + } + RET(FALSE); +} + +static gboolean +mouse_watch(panel *p) +{ + gint x, y; + + ENTER; + gdk_display_get_pointer(gdk_display_get_default(), NULL, &x, &y, NULL); + +/* Reduce sensitivity area + p->ah_far = ((x < p->cx - GAP) || (x > p->cx + p->cw + GAP) + || (y < p->cy - GAP) || (y > p->cy + p->ch + GAP)); +*/ + + gint cx, cy, cw, ch; + + cx = p->cx; + cy = p->cy; + cw = p->aw; + ch = p->ah; + + /* reduce area which will raise panel so it does not interfere with apps */ + if (p->ah_state == ah_state_hidden) { + switch (p->edge) { + case EDGE_LEFT: + cw = GAP; + break; + case EDGE_RIGHT: + cx = cx + cw - GAP; + cw = GAP; + break; + case EDGE_TOP: + ch = GAP; + break; + case EDGE_BOTTOM: + cy = cy + ch - GAP; + ch = GAP; + break; + } + } + p->ah_far = ((x < cx) || (x > cx + cw) || (y < cy) || (y > cy + ch)); + + p->ah_state(p); + RET(TRUE); +} + +static gboolean +ah_state_visible(panel *p) +{ + ENTER; + if (p->ah_state != ah_state_visible) { + p->ah_state = ah_state_visible; + gtk_widget_show(p->topgwin); + gtk_window_stick(GTK_WINDOW(p->topgwin)); + } else if (p->ah_far) { + ah_state_waiting(p); + } + RET(FALSE); +} + +static gboolean +ah_state_waiting(panel *p) +{ + ENTER; + if (p->ah_state != ah_state_waiting) { + p->ah_state = ah_state_waiting; + hpid = g_timeout_add(2 * PERIOD, (GSourceFunc) ah_state_hidden, p); + } else if (!p->ah_far) { + g_source_remove(hpid); + hpid = 0; + ah_state_visible(p); + } + RET(FALSE); +} + +static gboolean +ah_state_hidden(panel *p) +{ + ENTER; + if (p->ah_state != ah_state_hidden) { + p->ah_state = ah_state_hidden; + gtk_widget_hide(p->topgwin); + } else if (!p->ah_far) { + ah_state_visible(p); + } + RET(FALSE); +} + +/* starts autohide behaviour */ +void +ah_start(panel *p) +{ + ENTER; + mwid = g_timeout_add(PERIOD, (GSourceFunc) mouse_watch, p); + ah_state_visible(p); + RET(); +} + +/* stops autohide */ +void +ah_stop(panel *p) +{ + ENTER; + if (mwid) { + g_source_remove(mwid); + mwid = 0; + } + if (hpid) { + g_source_remove(hpid); + hpid = 0; + } + RET(); +} + +/**************************************************** + * panel creation * + ****************************************************/ +void +about() +{ + gchar *authors[] = { "Anatoly Asviyan ", NULL }; + + ENTER; + gtk_show_about_dialog(NULL, + "authors", authors, + "comments", "Lightweight GTK+ desktop panel", + "license", "GPLv2", + "program-name", PROJECT_NAME, + "version", PROJECT_VERSION, + "website", "http://fbpanel.sf.net", + "logo-icon-name", "logo", + "translator-credits", _("translator-credits"), + NULL); + RET(); +} + +static GtkWidget * +panel_make_menu(panel *p) +{ + GtkWidget *mi, *menu; + + ENTER; + menu = gtk_menu_new(); + + /* panel's preferences */ + mi = gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, NULL); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); + g_signal_connect_swapped(G_OBJECT(mi), "activate", + (GCallback)configure, p->xc); + gtk_widget_show (mi); + + /* separator */ + mi = gtk_separator_menu_item_new(); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); + gtk_widget_show (mi); + + /* about */ + mi = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, NULL); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); + g_signal_connect(G_OBJECT(mi), "activate", + (GCallback)about, p); + gtk_widget_show (mi); + + RET(menu); +} + +gboolean +panel_button_press_event(GtkWidget *widget, GdkEventButton *event, panel *p) +{ + ENTER; + if (event->type == GDK_BUTTON_PRESS && event->button == 3 + && event->state & GDK_CONTROL_MASK) { + DBG("ctrl-btn3\n"); + gtk_menu_popup (GTK_MENU (p->menu), NULL, NULL, NULL, + NULL, event->button, event->time); + RET(TRUE); + } + RET(FALSE); +} + +static gboolean +panel_scroll_event(GtkWidget *widget, GdkEventScroll *event, panel *p) +{ + int i; + + ENTER; + DBG("scroll direction = %d\n", event->direction); + i = p->curdesk; + if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_LEFT) { + i--; + if (i < 0) + i = p->desknum - 1; + } else { + i++; + if (i >= p->desknum) + i = 0; + } + Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, i, 0, 0, 0, 0); + RET(TRUE); +} + + +static void +panel_start_gui(panel *p) +{ + ENTER; + + // main toplevel window + p->topgwin = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width(GTK_CONTAINER(p->topgwin), 0); + g_signal_connect(G_OBJECT(p->topgwin), "destroy-event", + (GCallback) panel_destroy_event, p); + g_signal_connect(G_OBJECT(p->topgwin), "size-request", + (GCallback) panel_size_req, p); + g_signal_connect(G_OBJECT(p->topgwin), "map-event", + (GCallback) panel_mapped, p); + g_signal_connect(G_OBJECT(p->topgwin), "configure-event", + (GCallback) panel_configure_event, p); + g_signal_connect(G_OBJECT(p->topgwin), "button-press-event", + (GCallback) panel_button_press_event, p); + g_signal_connect(G_OBJECT(p->topgwin), "scroll-event", + (GCallback) panel_scroll_event, p); + + gtk_window_set_resizable(GTK_WINDOW(p->topgwin), FALSE); + gtk_window_set_wmclass(GTK_WINDOW(p->topgwin), "panel", "fbpanel"); + gtk_window_set_title(GTK_WINDOW(p->topgwin), "panel"); + gtk_window_set_position(GTK_WINDOW(p->topgwin), GTK_WIN_POS_NONE); + gtk_window_set_decorated(GTK_WINDOW(p->topgwin), FALSE); + gtk_window_set_accept_focus(GTK_WINDOW(p->topgwin), FALSE); + if (p->setdocktype) + gtk_window_set_type_hint(GTK_WINDOW(p->topgwin), + GDK_WINDOW_TYPE_HINT_DOCK); + + if (p->layer == LAYER_ABOVE) + gtk_window_set_keep_above(GTK_WINDOW(p->topgwin), TRUE); + else if (p->layer == LAYER_BELOW) + gtk_window_set_keep_below(GTK_WINDOW(p->topgwin), TRUE); + gtk_window_stick(GTK_WINDOW(p->topgwin)); + + gtk_widget_realize(p->topgwin); + p->topxwin = GDK_WINDOW_XWINDOW(p->topgwin->window); + DBG("topxwin = %lx\n", p->topxwin); + /* ensure configure event */ + XMoveWindow(GDK_DISPLAY(), p->topxwin, 20, 20); + XSync(GDK_DISPLAY(), False); + + gtk_widget_set_app_paintable(p->topgwin, TRUE); + calculate_position(p); + gtk_window_move(GTK_WINDOW(p->topgwin), p->ax, p->ay); + gtk_window_resize(GTK_WINDOW(p->topgwin), p->aw, p->ah); + DBG("move-resize x %d y %d w %d h %d\n", p->ax, p->ay, p->aw, p->ah); + //XSync(GDK_DISPLAY(), False); + //gdk_flush(); + + // background box all over toplevel + p->bbox = gtk_bgbox_new(); + gtk_container_add(GTK_CONTAINER(p->topgwin), p->bbox); + gtk_container_set_border_width(GTK_CONTAINER(p->bbox), 0); + if (p->transparent) { + p->bg = fb_bg_get_for_display(); + gtk_bgbox_set_background(p->bbox, BG_ROOT, p->tintcolor, p->alpha); + } + + // main layout manager as a single child of background widget box + p->lbox = p->my_box_new(FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(p->lbox), 0); + gtk_container_add(GTK_CONTAINER(p->bbox), p->lbox); + + p->box = p->my_box_new(FALSE, p->spacing); + gtk_container_set_border_width(GTK_CONTAINER(p->box), 0); + gtk_box_pack_start(GTK_BOX(p->lbox), p->box, TRUE, TRUE, + (p->round_corners) ? p->round_corners_radius : 0); + if (p->round_corners) { + make_round_corners(p); + DBG("make_round_corners\n"); + } + /* start window creation process */ + gtk_widget_show_all(p->topgwin); + /* .. and hide it from user until everything is done */ + gtk_widget_hide(p->topgwin); + + p->menu = panel_make_menu(p); + + if (p->setstrut) + panel_set_wm_strut(p); + + XSelectInput(GDK_DISPLAY(), GDK_ROOT_WINDOW(), PropertyChangeMask); + gdk_window_add_filter(gdk_get_default_root_window(), + (GdkFilterFunc)panel_event_filter, p); + //XSync(GDK_DISPLAY(), False); + gdk_flush(); + RET(); +} + +static int +panel_parse_global(xconf *xc) +{ + ENTER; + /* Set default values */ + p->allign = ALLIGN_CENTER; + p->edge = EDGE_BOTTOM; + p->widthtype = WIDTH_PERCENT; + p->width = 100; + p->heighttype = HEIGHT_PIXEL; + p->height = PANEL_HEIGHT_DEFAULT; + p->max_elem_height = PANEL_HEIGHT_MAX; + p->setdocktype = 1; + p->setstrut = 1; + p->round_corners = 1; + p->round_corners_radius = 7; + p->autohide = 0; + p->height_when_hidden = 2; + p->transparent = 0; + p->alpha = 127; + p->tintcolor_name = "white"; + p->spacing = 0; + p->setlayer = FALSE; + p->layer = LAYER_ABOVE; + + /* Read config */ + /* geometry */ + XCG(xc, "edge", &p->edge, enum, edge_enum); + XCG(xc, "allign", &p->allign, enum, allign_enum); + XCG(xc, "widthtype", &p->widthtype, enum, widthtype_enum); + XCG(xc, "heighttype", &p->heighttype, enum, heighttype_enum); + XCG(xc, "width", &p->width, int); + XCG(xc, "height", &p->height, int); + XCG(xc, "margin", &p->margin, int); + + /* properties */ + XCG(xc, "setdocktype", &p->setdocktype, enum, bool_enum); + XCG(xc, "setpartialstrut", &p->setstrut, enum, bool_enum); + XCG(xc, "autohide", &p->autohide, enum, bool_enum); + XCG(xc, "heightwhenhidden", &p->height_when_hidden, int); + XCG(xc, "setlayer", &p->setlayer, enum, bool_enum); + XCG(xc, "layer", &p->layer, enum, layer_enum); + + /* effects */ + XCG(xc, "roundcorners", &p->round_corners, enum, bool_enum); + XCG(xc, "roundcornersradius", &p->round_corners_radius, int); + XCG(xc, "transparent", &p->transparent, enum, bool_enum); + XCG(xc, "alpha", &p->alpha, int); + XCG(xc, "tintcolor", &p->tintcolor_name, str); + XCG(xc, "maxelemheight", &p->max_elem_height, int); + + /* Sanity checks */ + if (!gdk_color_parse(p->tintcolor_name, &p->gtintcolor)) + gdk_color_parse("white", &p->gtintcolor); + p->tintcolor = gcolor2rgb24(&p->gtintcolor); + DBG("tintcolor=%x\n", p->tintcolor); + if (p->alpha > 255) + p->alpha = 255; + p->orientation = (p->edge == EDGE_TOP || p->edge == EDGE_BOTTOM) + ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL; + if (p->orientation == GTK_ORIENTATION_HORIZONTAL) { + p->my_box_new = gtk_hbox_new; + p->my_separator_new = gtk_vseparator_new; + } else { + p->my_box_new = gtk_vbox_new; + p->my_separator_new = gtk_hseparator_new; + } + if (p->width < 0) + p->width = 100; + if (p->widthtype == WIDTH_PERCENT && p->width > 100) + p->width = 100; + p->heighttype = HEIGHT_PIXEL; + if (p->heighttype == HEIGHT_PIXEL) { + if (p->height < PANEL_HEIGHT_MIN) + p->height = PANEL_HEIGHT_MIN; + else if (p->height > PANEL_HEIGHT_MAX) + p->height = PANEL_HEIGHT_MAX; + } + if (p->max_elem_height > p->height || + p->max_elem_height < PANEL_HEIGHT_MIN) + p->max_elem_height = p->height; + p->curdesk = get_net_current_desktop(); + p->desknum = get_net_number_of_desktops(); + //p->workarea = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_WORKAREA, + // XA_CARDINAL, &p->wa_len); + //print_wmdata(p); + panel_start_gui(p); + RET(1); +} + +static void +panel_parse_plugin(xconf *xc) +{ + plugin_instance *plug = NULL; + gchar *type = NULL; + + ENTER; + xconf_get_str(xconf_find(xc, "type", 0), &type); + if (!type || !(plug = plugin_load(type))) { + ERR( "fbpanel: can't load %s plugin\n", type); + return; + } + plug->panel = p; + XCG(xc, "expand", &plug->expand, enum, bool_enum); + XCG(xc, "padding", &plug->padding, int); + XCG(xc, "border", &plug->border, int); + plug->xc = xconf_find(xc, "config", 0); + + if (!plugin_start(plug)) { + ERR( "fbpanel: can't start plugin %s\n", type); + exit(1); + } + p->plugins = g_list_append(p->plugins, plug); +} + +static void +panel_start(xconf *xc) +{ + int i; + xconf *pxc; + + ENTER; + fbev = fb_ev_new(); + + //xconf_prn(stdout, xc, 0, FALSE); + panel_parse_global(xconf_find(xc, "global", 0)); + for (i = 0; (pxc = xconf_find(xc, "plugin", i)); i++) + panel_parse_plugin(pxc); + RET(); +} + +static void +delete_plugin(gpointer data, gpointer udata) +{ + ENTER; + plugin_stop((plugin_instance *)data); + plugin_put((plugin_instance *)data); + RET(); +} + +static void +panel_stop(panel *p) +{ + ENTER; + + if (p->autohide) + ah_stop(p); + g_list_foreach(p->plugins, delete_plugin, NULL); + g_list_free(p->plugins); + p->plugins = NULL; + + XSelectInput(GDK_DISPLAY(), GDK_ROOT_WINDOW(), NoEventMask); + gdk_window_remove_filter(gdk_get_default_root_window(), + (GdkFilterFunc)panel_event_filter, p); + gtk_widget_destroy(p->topgwin); + gtk_widget_destroy(p->menu); + g_object_unref(fbev); + //g_free(p->workarea); + gdk_flush(); + XFlush(GDK_DISPLAY()); + XSync(GDK_DISPLAY(), True); + RET(); +} + +void +usage() +{ + ENTER; + printf("fbpanel %s - lightweight GTK2+ panel for UNIX desktops\n", version); + printf("Command line options:\n"); + printf(" --help -- print this help and exit\n"); + printf(" --version -- print version and exit\n"); + printf(" --log -- set log level 0-5. 0 - none 5 - chatty\n"); + printf(" --configure -- launch configuration utility\n"); + printf(" --profile name -- use specified profile\n"); + printf("\n"); + printf(" -h -- same as --help\n"); + printf(" -p -- same as --profile\n"); + printf(" -v -- same as --version\n"); + printf(" -C -- same as --configure\n"); + printf("\nVisit http://fbpanel.sourceforge.net/ for detailed documentation,\n\n"); +} + +void +handle_error(Display * d, XErrorEvent * ev) +{ + char buf[256]; + + ENTER; + XGetErrorText(GDK_DISPLAY(), ev->error_code, buf, 256); + DBG("fbpanel : X error: %s\n", buf); + + RET(); +} + +static void +sig_usr1(int signum) +{ + if (signum != SIGUSR1) + return; + gtk_main_quit(); +} + +static void +sig_usr2(int signum) +{ + if (signum != SIGUSR2) + return; + gtk_main_quit(); + force_quit = 1; +} + +static void +do_argv(int argc, char *argv[]) +{ + int i; + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { + usage(); + exit(0); + } else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--version")) { + printf("fbpanel %s\n", version); + exit(0); + } else if (!strcmp(argv[i], "--log")) { + i++; + if (i == argc) { + ERR( "fbpanel: missing log level\n"); + usage(); + exit(1); + } else { + log_level = atoi(argv[i]); + } + } else if (!strcmp(argv[i], "--configure") || !strcmp(argv[i], "-C")) { + config = 1; + } else if (!strcmp(argv[i], "--profile") || !strcmp(argv[i], "-p")) { + i++; + if (i == argc) { + ERR( "fbpanel: missing profile name\n"); + usage(); + exit(1); + } else { + profile = g_strdup(argv[i]); + } + } else { + printf("fbpanel: unknown option - %s\n", argv[i]); + usage(); + exit(1); + } + } +} + +gchar *panel_get_profile() +{ + return profile; +} + +gchar *panel_get_profile_file() +{ + return profile_file; +} + +static void +ensure_profile() +{ + gchar *cmd; + + if (g_file_test(profile_file, + G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) + { + return; + } + cmd = g_strdup_printf("%s %s", LIBEXECDIR "/fbpanel/make_profile", + profile); + g_spawn_command_line_sync(cmd, NULL, NULL, NULL, NULL); + g_free(cmd); + if (g_file_test(profile_file, + G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) + { + return; + } + ERR("Can't open profile %s - %s\n", profile, profile_file); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + setlocale(LC_CTYPE, ""); + bindtextdomain(PROJECT_NAME, LOCALEDIR); + textdomain(PROJECT_NAME); + + gtk_set_locale(); + gtk_init(&argc, &argv); + XSetLocaleModifiers(""); + XSetErrorHandler((XErrorHandler) handle_error); + fb_init(); + do_argv(argc, argv); + profile_file = g_build_filename(g_get_user_config_dir(), + "fbpanel", profile, NULL); + ensure_profile(); + gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), IMGPREFIX); + signal(SIGUSR1, sig_usr1); + signal(SIGUSR2, sig_usr2); + + do { + the_panel = p = g_new0(panel, 1); + p->xc = xconf_new_from_file(profile_file, profile); + if (!p->xc) + exit(1); + + panel_start(p->xc); + if (config) + configure(p->xc); + gtk_main(); + panel_stop(p); + //xconf_save_to_profile(cprofile, xc); + xconf_del(p->xc, FALSE); + g_free(p); + DBG("force_quit=%d\n", force_quit); + } while (force_quit == 0); + g_free(profile_file); + fb_free(); + exit(0); +} + diff --git a/panel/panel.h b/panel/panel.h new file mode 100644 index 0000000..4dac183 --- /dev/null +++ b/panel/panel.h @@ -0,0 +1,193 @@ +#ifndef PANEL_H +#define PANEL_H + + +#include +#include +#include + +#include +#define _(String) gettext(String) +#define c_(String) String + +#include "config.h" + +#include "bg.h" +#include "ev.h" +#include "xconf.h" + +enum { ALLIGN_CENTER, ALLIGN_LEFT, ALLIGN_RIGHT }; +enum { EDGE_BOTTOM, EDGE_LEFT, EDGE_RIGHT, EDGE_TOP }; +enum { WIDTH_PERCENT, WIDTH_REQUEST, WIDTH_PIXEL }; +enum { HEIGHT_PIXEL, HEIGHT_REQUEST }; +enum { POS_NONE, POS_START, POS_END }; +enum { HIDDEN, WAITING, VISIBLE }; +enum { LAYER_ABOVE, LAYER_BELOW }; + +#define PANEL_HEIGHT_DEFAULT 26 +#define PANEL_HEIGHT_MAX 200 +#define PANEL_HEIGHT_MIN 16 + +#define IMGPREFIX DATADIR "/images" + +typedef struct _panel +{ + GtkWidget *topgwin; /* main panel window */ + Window topxwin; /* and it X window */ + GtkWidget *lbox; /* primary layout box */ + GtkWidget *bbox; /* backgound box for box */ + GtkWidget *box; /* box that contains all plugins */ + GtkWidget *menu; /* context menu */ + GtkRequisition requisition; + GtkWidget *(*my_box_new) (gboolean, gint); + GtkWidget *(*my_separator_new) (); + + FbBg *bg; + int alpha; + guint32 tintcolor; + GdkColor gtintcolor; + gchar *tintcolor_name; + + int ax, ay, aw, ah; /* prefferd allocation of a panel */ + int cx, cy, cw, ch; /* current allocation (as reported by configure event) allocation */ + int allign, edge, margin; + GtkOrientation orientation; + int widthtype, width; + int heighttype, height; + int round_corners_radius; + int max_elem_height; + + gint self_destroy; + gint setdocktype; + gint setstrut; + gint round_corners; + gint transparent; + gint autohide; + gint ah_far; + gint layer; + gint setlayer; + + int ah_dx, ah_dy; // autohide shift for x and y + int height_when_hidden; + guint hide_tout; + + int spacing; + + guint desknum; + guint curdesk; + guint32 *workarea; + + int plug_num; + GList *plugins; + + gboolean (*ah_state)(struct _panel *); + + xconf *xc; +} panel; + + +typedef struct { + unsigned int modal : 1; + unsigned int sticky : 1; + unsigned int maximized_vert : 1; + unsigned int maximized_horz : 1; + unsigned int shaded : 1; + unsigned int skip_taskbar : 1; + unsigned int skip_pager : 1; + unsigned int hidden : 1; + unsigned int fullscreen : 1; + unsigned int above : 1; + unsigned int below : 1; +} net_wm_state; + +typedef struct { + unsigned int desktop : 1; + unsigned int dock : 1; + unsigned int toolbar : 1; + unsigned int menu : 1; + unsigned int utility : 1; + unsigned int splash : 1; + unsigned int dialog : 1; + unsigned int normal : 1; +} net_wm_window_type; + +typedef struct { + char *name; + void (*cmd)(void); +} command; + +extern command commands[]; + +extern gchar *cprofile; + +extern Atom a_UTF8_STRING; +extern Atom a_XROOTPMAP_ID; + +extern Atom a_WM_STATE; +extern Atom a_WM_CLASS; +extern Atom a_WM_DELETE_WINDOW; +extern Atom a_WM_PROTOCOLS; +extern Atom a_NET_WORKAREA; +extern Atom a_NET_CLIENT_LIST; +extern Atom a_NET_CLIENT_LIST_STACKING; +extern Atom a_NET_NUMBER_OF_DESKTOPS; +extern Atom a_NET_CURRENT_DESKTOP; +extern Atom a_NET_DESKTOP_NAMES; +extern Atom a_NET_DESKTOP_GEOMETRY; +extern Atom a_NET_ACTIVE_WINDOW; +extern Atom a_NET_CLOSE_WINDOW; +extern Atom a_NET_SUPPORTED; +extern Atom a_NET_WM_STATE; +extern Atom a_NET_WM_STATE_SKIP_TASKBAR; +extern Atom a_NET_WM_STATE_SKIP_PAGER; +extern Atom a_NET_WM_STATE_STICKY; +extern Atom a_NET_WM_STATE_HIDDEN; +extern Atom a_NET_WM_STATE_SHADED; +extern Atom a_NET_WM_STATE_ABOVE; +extern Atom a_NET_WM_STATE_BELOW; + +#define a_NET_WM_STATE_REMOVE 0 /* remove/unset property */ +#define a_NET_WM_STATE_ADD 1 /* add/set property */ +#define a_NET_WM_STATE_TOGGLE 2 /* toggle property */ + +extern Atom a_NET_WM_WINDOW_TYPE; +extern Atom a_NET_WM_WINDOW_TYPE_DESKTOP; +extern Atom a_NET_WM_WINDOW_TYPE_DOCK; +extern Atom a_NET_WM_WINDOW_TYPE_TOOLBAR; +extern Atom a_NET_WM_WINDOW_TYPE_MENU; +extern Atom a_NET_WM_WINDOW_TYPE_UTILITY; +extern Atom a_NET_WM_WINDOW_TYPE_SPLASH; +extern Atom a_NET_WM_WINDOW_TYPE_DIALOG; +extern Atom a_NET_WM_WINDOW_TYPE_NORMAL; + +extern Atom a_NET_WM_DESKTOP; +extern Atom a_NET_WM_NAME; +extern Atom a_NET_WM_VISIBLE_NAME; +extern Atom a_NET_WM_STRUT; +extern Atom a_NET_WM_STRUT_PARTIAL; +extern Atom a_NET_WM_ICON; +extern Atom a_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR; + + +extern xconf_enum allign_enum[]; +extern xconf_enum edge_enum[]; +extern xconf_enum widthtype_enum[]; +extern xconf_enum heighttype_enum[]; +extern xconf_enum bool_enum[]; +extern xconf_enum pos_enum[]; +extern xconf_enum layer_enum[]; + +extern int verbose; +extern gint force_quit; +extern FbEv *fbev; +extern GtkIconTheme *icon_theme; +#define FBPANEL_WIN(win) gdk_window_lookup(win) +void panel_set_wm_strut(panel *p); + +gchar *panel_get_profile(void); +gchar *panel_get_profile_file(void); + +void ah_start(panel *p); +void ah_stop(panel *p); + +#endif diff --git a/panel/plugin.c b/panel/plugin.c new file mode 100644 index 0000000..10be038 --- /dev/null +++ b/panel/plugin.c @@ -0,0 +1,233 @@ + +#include "plugin.h" + +#include +#include +#include +#include +#include + + + +#include "misc.h" +#include "bg.h" +#include "gtkbgbox.h" + + +//#define DEBUGPRN +#include "dbg.h" +extern panel *the_panel; + + +/**************************************************************/ +static GHashTable *class_ht; + + +void +class_register(plugin_class *p) +{ + ENTER; + if (!class_ht) { + class_ht = g_hash_table_new(g_str_hash, g_str_equal); + DBG("creating class hash table\n"); + } + DBG("registering %s\n", p->type); + if (g_hash_table_lookup(class_ht, p->type)) { + ERR("Can't register plugin %s. Such name already exists.\n", p->type); + exit(1); + } + p->dynamic = (the_panel != NULL); /* dynamic modules register after main */ + g_hash_table_insert(class_ht, p->type, p); + RET(); +} + +void +class_unregister(plugin_class *p) +{ + ENTER; + DBG("unregistering %s\n", p->type); + if (!g_hash_table_remove(class_ht, p->type)) { + ERR("Can't unregister plugin %s. No such name\n", p->type); + } + if (!g_hash_table_size(class_ht)) { + DBG("dwstroying class hash table\n"); + g_hash_table_destroy(class_ht); + class_ht = NULL; + } + RET(); +} + +void +class_put(char *name) +{ + GModule *m; + gchar *s; + plugin_class *tmp; + + ENTER; + DBG("%s\n", name); + if (!(class_ht && (tmp = g_hash_table_lookup(class_ht, name)))) + RET(); + tmp->count--; + if (tmp->count || !tmp->dynamic) + RET(); + + s = g_strdup_printf(LIBDIR "/lib%s.so", name); + DBG("loading module %s\n", s); + m = g_module_open(s, G_MODULE_BIND_LAZY); + g_free(s); + if (m) { + /* Close it twise to undo initial open in class_get */ + g_module_close(m); + g_module_close(m); + } + RET(); +} + +gpointer +class_get(char *name) +{ + GModule *m; + plugin_class *tmp; + gchar *s; + + ENTER; + DBG("%s\n", name); + if (class_ht && (tmp = g_hash_table_lookup(class_ht, name))) { + DBG("found\n"); + tmp->count++; + RET(tmp); + } + s = g_strdup_printf(LIBDIR "/lib%s.so", name); + DBG("loading module %s\n", s); + m = g_module_open(s, G_MODULE_BIND_LAZY); + g_free(s); + if (m) { + if (class_ht && (tmp = g_hash_table_lookup(class_ht, name))) { + DBG("found\n"); + tmp->count++; + RET(tmp); + } + } + ERR("%s\n", g_module_error()); + RET(NULL); +} + + + +/**************************************************************/ + +plugin_instance * +plugin_load(char *type) +{ + plugin_class *pc = NULL; + plugin_instance *pp = NULL; + + ENTER; + /* nothing was found */ + if (!(pc = class_get(type))) + RET(NULL); + + DBG("%s priv_size=%d\n", pc->type, pc->priv_size); + pp = g_malloc0(pc->priv_size); + g_return_val_if_fail (pp != NULL, NULL); + pp->class = pc; + RET(pp); +} + + +void +plugin_put(plugin_instance *this) +{ + gchar *type; + + ENTER; + type = this->class->type; + g_free(this); + class_put(type); + RET(); +} + +gboolean panel_button_press_event(GtkWidget *widget, GdkEventButton *event, + panel *p); + +int +plugin_start(plugin_instance *this) +{ + ENTER; + + DBG("%s\n", this->class->type); + if (!this->class->invisible) { + this->pwid = gtk_bgbox_new(); + gtk_widget_set_name(this->pwid, this->class->type); + gtk_box_pack_start(GTK_BOX(this->panel->box), this->pwid, this->expand, + TRUE, this->padding); + DBG("%s expand %d\n", this->class->type, this->expand); + gtk_container_set_border_width(GTK_CONTAINER(this->pwid), this->border); + DBG("here this->panel->transparent = %d\n", this->panel->transparent); + if (this->panel->transparent) { + DBG("here g\n"); + gtk_bgbox_set_background(this->pwid, BG_INHERIT, + this->panel->tintcolor, this->panel->alpha); + } + DBG("here\n"); + g_signal_connect (G_OBJECT (this->pwid), "button-press-event", + (GCallback) panel_button_press_event, this->panel); + gtk_widget_show(this->pwid); + DBG("here\n"); + } else { + /* create a no-window widget and do not show it it's usefull to have + * unmaped widget for invisible plugins so their indexes in plugin list + * are the same as in panel->box. required for children reordering */ + this->pwid = gtk_vbox_new(TRUE, 0); + gtk_box_pack_start(GTK_BOX(this->panel->box), this->pwid, FALSE, + TRUE,0); + gtk_widget_hide(this->pwid); + } + DBG("here\n"); + if (!this->class->constructor(this)) { + DBG("here\n"); + gtk_widget_destroy(this->pwid); + RET(0); + } + RET(1); +} + + +void +plugin_stop(plugin_instance *this) +{ + ENTER; + DBG("%s\n", this->class->type); + this->class->destructor(this); + this->panel->plug_num--; + gtk_widget_destroy(this->pwid); + RET(); +} + + +GtkWidget * +default_plugin_edit_config(plugin_instance *pl) +{ + GtkWidget *vbox, *label; + gchar *msg; + + ENTER; + vbox = gtk_vbox_new(FALSE, 0); + /* XXX: harcoded default profile name */ + msg = g_strdup_printf("Graphical '%s' plugin configuration\n is not " + "implemented yet.\n" + "Please edit manually\n\t~/.config/fbpanel/default\n\n" + "You can use as example files in \n\t%s/share/fbpanel/\n" + "or visit\n" + "\thttp://fbpanel.sourceforge.net/docs.html", pl->class->name, + PREFIX); + label = gtk_label_new(msg); + gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); + gtk_label_set_selectable(GTK_LABEL(label), TRUE); + gtk_box_pack_end(GTK_BOX(vbox), label, TRUE, TRUE, 5); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 14); + g_free(msg); + + RET(vbox); +} diff --git a/panel/plugin.h b/panel/plugin.h new file mode 100644 index 0000000..23e43be --- /dev/null +++ b/panel/plugin.h @@ -0,0 +1,67 @@ + +#ifndef PLUGIN_H +#define PLUGIN_H +#include + + +#include +#include +#include +#include "panel.h" + +struct _plugin_instance *stam; + +typedef struct { + /* common */ + char *fname; + int count; + GModule *gmodule; + + int dynamic : 1; + int invisible : 1; + /* these fields are pointers to the data within loaded dll */ + char *type; + char *name; + char *version; + char *description; + int priv_size; + + int (*constructor)(struct _plugin_instance *this); + void (*destructor)(struct _plugin_instance *this); + void (*save_config)(struct _plugin_instance *this, FILE *fp); + GtkWidget *(*edit_config)(struct _plugin_instance *this); +} plugin_class; + +#define PLUGIN_CLASS(class) ((plugin_class *) class) + +typedef struct _plugin_instance{ + plugin_class *class; + panel *panel; + xconf *xc; + GtkWidget *pwid; + int expand; + int padding; + int border; +} plugin_instance; + +void class_put(char *name); +gpointer class_get(char *name); +/* if plugin_instance is external it will load its dll */ +plugin_instance * plugin_load(char *type); +void plugin_put(plugin_instance *this); +int plugin_start(plugin_instance *this); +void plugin_stop(plugin_instance *this); +GtkWidget *default_plugin_instance_edit_config(plugin_instance *pl); + +void class_register(plugin_class *p); +void class_unregister(plugin_class *p); + +#ifdef PLUGIN +static plugin_class *class_ptr; +static void ctor(void) __attribute__ ((constructor)); +static void ctor(void) { class_register(class_ptr); } +static void dtor(void) __attribute__ ((destructor)); +static void dtor(void) { class_unregister(class_ptr); } +#endif + +#endif diff --git a/panel/run.c b/panel/run.c new file mode 100644 index 0000000..342c2a5 --- /dev/null +++ b/panel/run.c @@ -0,0 +1,24 @@ +#include "run.h" +#include "dbg.h" + +void +run_app(gchar *cmd) +{ + GError *error = NULL; + + ENTER; + if (!cmd) + RET(); + + if (!g_spawn_command_line_async(cmd, &error)) + { + GtkWidget *dialog = gtk_message_dialog_new(NULL, 0, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", error->message); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + g_error_free(error); + } + RET(); +} diff --git a/panel/run.h b/panel/run.h new file mode 100644 index 0000000..6093116 --- /dev/null +++ b/panel/run.h @@ -0,0 +1,9 @@ +#ifndef _RUN_H_ +#define _RUN_H_ + +#include + +void run_app(gchar *cmd); +void run_command(gchar *cmd); + +#endif diff --git a/panel/xconf.c b/panel/xconf.c new file mode 100644 index 0000000..a207267 --- /dev/null +++ b/panel/xconf.c @@ -0,0 +1,389 @@ +#include +#include +#include +#include + +#include "xconf.h" +#include "panel.h" + +//#define DEBUGPRN +#include "dbg.h" + + +enum { LINE_NONE, LINE_BLOCK_START, LINE_BLOCK_END, LINE_VAR }; + +#define LINE_LENGTH 256 +typedef struct { + int type; + gchar str[LINE_LENGTH]; + gchar *t[2]; +} line; + + +/* Creates new xconf node */ +xconf *xconf_new(gchar *name, gchar *value) +{ + xconf *x; + + x = g_new0(xconf, 1); + x->name = g_strdup(name); + x->value = g_strdup(value); + return x; +} + +/* Append @src's sons to @dst node */ +void xconf_append_sons(xconf *dst, xconf *src) +{ + GSList *e; + xconf *tmp; + + if (!dst || !src) + return; + for (e = src->sons; e; e = g_slist_next(e)) + { + tmp = e->data; + tmp->parent = dst; + } + dst->sons = g_slist_concat(dst->sons, src->sons); + src->sons = NULL; +} + +/* Adss new son node */ +void xconf_append(xconf *parent, xconf *son) +{ + if (!parent || !son) + return; + son->parent = parent; + /* appending requires traversing all list to the end, which is not + * efficient, but for v 1.0 it's ok*/ + parent->sons = g_slist_append(parent->sons, son); +} + +/* Unlinks node from its parent, if any */ +void xconf_unlink(xconf *x) +{ + if (x && x->parent) + { + x->parent->sons = g_slist_remove(x->parent->sons, x); + x->parent = NULL; + } +} + +/* Unlinks node and deletes it and its sub-tree */ +void xconf_del(xconf *x, gboolean sons_only) +{ + GSList *s; + xconf *x2; + + if (!x) + return; + DBG("%s %s\n", x->name, x->value); + for (s = x->sons; s; s = g_slist_delete_link(s, s)) + { + x2 = s->data; + x2->parent = NULL; + xconf_del(x2, FALSE); + } + x->sons = NULL; + if (!sons_only) + { + g_free(x->name); + g_free(x->value); + xconf_unlink(x); + g_free(x); + } +} + +void xconf_set_value(xconf *x, gchar *value) +{ + xconf_del(x, TRUE); + g_free(x->value); + x->value = g_strdup(value); + +} + +void xconf_set_value_ref(xconf *x, gchar *value) +{ + xconf_del(x, TRUE); + g_free(x->value); + x->value = value; + +} + +void xconf_set_int(xconf *x, int i) +{ + xconf_del(x, TRUE); + g_free(x->value); + x->value = g_strdup_printf("%d", i); +} + +xconf * +xconf_get(xconf *xc, gchar *name) +{ + xconf *ret; + + if (!xc) + return NULL; + if ((ret = xconf_find(xc, name, 0))) + return ret; + ret = xconf_new(name, NULL); + xconf_append(xc, ret); + return ret; +} + +gchar *xconf_get_value(xconf *x) +{ + return x->value; +} + +void xconf_prn(FILE *fp, xconf *x, int n, gboolean sons_only) +{ + int i; + GSList *s; + xconf *x2; + + if (!sons_only) + { + for (i = 0; i < n; i++) + fprintf(fp, " "); + fprintf(fp, "%s", x->name); + if (x->value) + fprintf(fp, " = %s\n", x->value); + else + fprintf(fp, " {\n"); + n++; + } + for (s = x->sons; s; s = g_slist_next(s)) + { + x2 = s->data; + xconf_prn(fp, x2, n, FALSE); + } + if (!sons_only && !x->value) + { + n--; + for (i = 0; i < n; i++) + fprintf(fp, " "); + fprintf(fp, "}\n"); + } + +} + +xconf *xconf_find(xconf *x, gchar *name, int no) +{ + GSList *s; + xconf *x2; + + if (!x) + return NULL; + for (s = x->sons; s; s = g_slist_next(s)) + { + x2 = s->data; + if (!strcasecmp(x2->name, name)) + { + if (!no) + return x2; + no--; + } + } + return NULL; +} + + +void xconf_get_str(xconf *x, gchar **val) +{ + if (x && x->value) + *val = x->value; +} + + +void xconf_get_int(xconf *x, int *val) +{ + gchar *s; + + if (!x) + return; + s = xconf_get_value(x); + if (!s) + return; + *val = strtol(s, NULL, 0); +} + +void xconf_get_enum(xconf *x, int *val, xconf_enum *p) +{ + gchar *s; + + if (!x) + return; + s = xconf_get_value(x); + if (!s) + return; + while (p && p->str) + { + DBG("cmp %s %s\n", p->str, s); + if (!strcasecmp(p->str, s)) + { + *val = p->num; + return; + } + p++; + } +} + +void +xconf_set_enum(xconf *x, int val, xconf_enum *p) +{ + if (!x) + return; + + while (p && p->str) + { + if (val == p->num) + { + xconf_set_value(x, p->str); + return; + } + p++; + } +} + +static int +read_line(FILE *fp, line *s) +{ + gchar *tmp, *tmp2; + + ENTER; + s->type = LINE_NONE; + if (!fp) + RET(s->type); + while (fgets(s->str, LINE_LENGTH, fp)) { + g_strstrip(s->str); + + if (s->str[0] == '#' || s->str[0] == 0) { + continue; + } + DBG( ">> %s\n", s->str); + if (!g_ascii_strcasecmp(s->str, "}")) { + s->type = LINE_BLOCK_END; + break; + } + + s->t[0] = s->str; + for (tmp = s->str; isalnum(*tmp); tmp++); + for (tmp2 = tmp; isspace(*tmp2); tmp2++); + if (*tmp2 == '=') { + for (++tmp2; isspace(*tmp2); tmp2++); + s->t[1] = tmp2; + *tmp = 0; + s->type = LINE_VAR; + } else if (*tmp2 == '{') { + *tmp = 0; + s->type = LINE_BLOCK_START; + } else { + ERR( "parser: unknown token: '%c'\n", *tmp2); + } + break; + } + RET(s->type); + +} + + +static xconf * +read_block(FILE *fp, gchar *name) +{ + line s; + xconf *x, *xs; + + x = xconf_new(name, NULL); + while (read_line(fp, &s) != LINE_NONE) + { + if (s.type == LINE_BLOCK_START) + { + xs = read_block(fp, s.t[0]); + xconf_append(x, xs); + } + else if (s.type == LINE_BLOCK_END) + break; + else if (s.type == LINE_VAR) + { + xs = xconf_new(s.t[0], s.t[1]); + xconf_append(x, xs); + } + else + { + printf("syntax error\n"); + exit(1); + } + } + return x; +} + +xconf *xconf_new_from_file(gchar *fname, gchar *name) +{ + FILE *fp = fopen(fname, "r"); + xconf *ret = NULL; + if (fp) + { + ret = read_block(fp, name); + fclose(fp); + } + //xconf_prn(stdout, ret, 0, FALSE); + return ret; +} + +void xconf_save_to_file(gchar *fname, xconf *xc) +{ + FILE *fp = fopen(fname, "w"); + + if (fp) + { + xconf_prn(fp, xc, 0, TRUE); + fclose(fp); + } +} + +void +xconf_save_to_profile(xconf *xc) +{ + xconf_save_to_file(panel_get_profile_file(), xc); +} + +xconf *xconf_dup(xconf *xc) +{ + xconf *ret, *son; + GSList *s; + + if (!xc) + return NULL; + ret = xconf_new(xc->name, xc->value); + for (s = xc->sons; s; s = g_slist_next(s)) + { + son = s->data; + xconf_append(ret, xconf_dup(son)); + } + return ret; +} + +gboolean +xconf_cmp(xconf *a, xconf *b) +{ + GSList *as, *bs; + + if (!(a || b)) + return FALSE; + if (!(a && b)) + return TRUE; + + if (g_ascii_strcasecmp(a->name, b->name)) + return TRUE; + + if (g_strcmp0(a->value, b->value)) + return TRUE; + for (as = a->sons, bs = b->sons; as && bs; + as = g_slist_next(as), bs = g_slist_next(bs)) + { + if (xconf_cmp(as->data, bs->data)) + return TRUE; + } + return (as != bs); +} diff --git a/panel/xconf.h b/panel/xconf.h new file mode 100644 index 0000000..b63ad06 --- /dev/null +++ b/panel/xconf.h @@ -0,0 +1,49 @@ +#ifndef _XCONF_H_ +#define _XCONF_H_ + +#include +#include + +typedef struct _xconf +{ + gchar *name; + gchar *value; + GSList *sons; + struct _xconf *parent; +} xconf; + +typedef struct { + gchar *str; + gchar *desc; + int num; +} xconf_enum; + +xconf *xconf_new(gchar *name, gchar *value); +void xconf_append(xconf *parent, xconf *son); +void xconf_append_sons(xconf *parent, xconf *son); +void xconf_unlink(xconf *x); +void xconf_del(xconf *x, gboolean sons_only); +void xconf_set_value(xconf *x, gchar *value); +void xconf_set_value_ref(xconf *x, gchar *value); +gchar *xconf_get_value(xconf *x); +void xconf_prn(FILE *fp, xconf *x, int n, gboolean sons_only); +xconf *xconf_find(xconf *x, gchar *name, int no); +xconf *xconf_dup(xconf *xc); +gboolean xconf_cmp(xconf *a, xconf *b); +xconf *xconf_new_from_file(gchar *fname, gchar *name); +void xconf_save_to_file(gchar *fname, xconf *xc); +void xconf_save_to_profile(xconf *xc); + +xconf *xconf_get(xconf *x, gchar *name); +void xconf_get_int(xconf *x, int *val); +void xconf_get_enum(xconf *x, int *val, xconf_enum *e); +void xconf_get_str(xconf *x, gchar **val); + +void xconf_set_int(xconf *x, int val); +void xconf_set_enum(xconf *x, int val, xconf_enum *e); + +#define XCG(xc, name, var, type, extra...) \ + xconf_get_ ## type(xconf_find(xc, name, 0), var, ## extra) + + +#endif diff --git a/plugins/Makefile b/plugins/Makefile new file mode 100644 index 0000000..346efd3 --- /dev/null +++ b/plugins/Makefile @@ -0,0 +1,29 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := .. + +SUBDIRS := battery \ + chart \ + cpu \ + dclock \ + deskno \ + deskno2 \ + genmon \ + icons \ + image \ + launchbar \ + mem \ + mem2 \ + menu \ + meter \ + net \ + pager \ + separator \ + space \ + systray \ + taskbar \ + tclock \ + volume \ + wincmd + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/battery/Makefile b/plugins/battery/Makefile new file mode 100644 index 0000000..5ddda22 --- /dev/null +++ b/plugins/battery/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +battery_src = battery.c +battery_cflags = -DPLUGIN $(GTK2_CFLAGS) +battery_libs = $(GTK2_LIBS) +battery_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/battery/battery.c b/plugins/battery/battery.c new file mode 100644 index 0000000..4f5b186 --- /dev/null +++ b/plugins/battery/battery.c @@ -0,0 +1,130 @@ +#include "misc.h" +#include "../meter/meter.h" +#include +#include +#include +#include + +//#define DEBUGPRN +#include "dbg.h" + +static meter_class *k; + +typedef struct { + meter_priv meter; + int timer; + gfloat level; + gboolean charging; + gboolean exist; +} battery_priv; + +static gboolean battery_update_os(battery_priv *c); + +static gchar *batt_working[] = { + "battery_0", + "battery_1", + "battery_2", + "battery_3", + "battery_4", + "battery_5", + "battery_6", + "battery_7", + "battery_8", + NULL +}; + +static gchar *batt_charging[] = { + "battery_charging_0", + "battery_charging_1", + "battery_charging_2", + "battery_charging_3", + "battery_charging_4", + "battery_charging_5", + "battery_charging_6", + "battery_charging_7", + "battery_charging_8", + NULL +}; + +static gchar *batt_na[] = { + "battery_na", + NULL +}; + +#if defined __linux__ +#include "os_linux.c" +#else + +static void +battery_update_os(battery_priv *c) +{ + c->exist = FALSE; +} + +#endif + +static gboolean +battery_update(battery_priv *c) +{ + gchar buf[50]; + gchar **i; + + ENTER; + battery_update_os(c); + if (c->exist) { + i = c->charging ? batt_charging : batt_working; + g_snprintf(buf, sizeof(buf), "Battery: %d%%%s", + (int) c->level, c->charging ? "\nCharging" : ""); + gtk_widget_set_tooltip_markup(((plugin_instance *)c)->pwid, buf); + } else { + i = batt_na; + gtk_widget_set_tooltip_markup(((plugin_instance *)c)->pwid, + "Runing on AC\nNo battery found"); + } + k->set_icons(&c->meter, i); + k->set_level(&c->meter, c->level); + RET(TRUE); +} + + +static int +battery_constructor(plugin_instance *p) +{ + battery_priv *c; + + ENTER; + if (!(k = class_get("meter"))) + RET(0); + if (!PLUGIN_CLASS(k)->constructor(p)) + RET(0); + c = (battery_priv *) p; + c->timer = g_timeout_add(2000, (GSourceFunc) battery_update, c); + battery_update(c); + RET(1); +} + +static void +battery_destructor(plugin_instance *p) +{ + battery_priv *c = (battery_priv *) p; + + ENTER; + if (c->timer) + g_source_remove(c->timer); + PLUGIN_CLASS(k)->destructor(p); + class_put("meter"); + RET(); +} + +static plugin_class class = { + .count = 0, + .type = "battery", + .name = "battery usage", + .version = "1.0", + .description = "Display battery usage", + .priv_size = sizeof(battery_priv), + .constructor = battery_constructor, + .destructor = battery_destructor, +}; + +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/battery/os_linux.c b/plugins/battery/os_linux.c new file mode 100644 index 0000000..34909b0 --- /dev/null +++ b/plugins/battery/os_linux.c @@ -0,0 +1,134 @@ + +#include +#include + +#define LEN 100 +#define PROC_ACPI "/proc/acpi/battery/" + + +static gboolean +get_token_eq(gchar *buf, gchar *token, gchar *value, gboolean *ret) +{ + int len; + gchar *var; + + ENTER; + len = strlen(token); + if (!(var = strstr(buf, token))) + RET(FALSE); + for (var = var + len; isspace(*var); var++) ; + *ret = !strncmp(var, value, strlen(value)); + RET(TRUE); +} + +static gboolean +get_token_int(gchar *buf, gchar *token, gint *value) +{ + int len; + gchar *var; + + ENTER; + len = strlen(token); + if (!(var = strstr(buf, token))) + RET(FALSE); + for (var = var + len; isspace(*var); var++) ; + if (sscanf(var, "%d", value) == 1) + RET(TRUE); + RET(FALSE); +} + +static gboolean +read_proc(battery_priv *c, GString *path) +{ + int len, lfcap, rcap; + gchar *buf; + gboolean ret, exist, charging; + + ENTER; + len = path->len; + + g_string_append(path, "/info"); + ret = g_file_get_contents(path->str, &buf, 0, NULL); + DBG("reading %s %s\n", path->str, ret ? "ok" : "fail"); + g_string_truncate(path, len); + if (!ret) + RET(FALSE); + ret = get_token_eq(buf, "present:", "yes", &exist) + && exist && get_token_int(buf, "last full capacity:", &lfcap); + + g_free(buf); + if (!ret) + RET(FALSE); + + g_string_append(path, "/state"); + ret = g_file_get_contents(path->str, &buf, 0, NULL); + DBG("reading %s %s\n", path->str, ret ? "ok" : "fail"); + g_string_truncate(path, len); + if (!ret) + RET(FALSE); + ret = get_token_eq(buf, "present:", "yes", &exist) + && exist + && get_token_int(buf, "remaining capacity:", &rcap) + && get_token_eq(buf, "charging state:", "charging", &charging); + g_free(buf); + if (!ret) + RET(FALSE); + DBG("battery=%s\nlast full capacity=%d\nremaining capacity=%d\n" + "charging=%d\n", + path->str, lfcap, rcap, charging); + + if (!(lfcap >= rcap && lfcap > 0 && rcap >= 0)) + RET(FALSE); + + c->exist = TRUE; + c->charging = charging; + c->level = (int) ((gfloat) rcap * 100 / (gfloat) lfcap); + RET(TRUE); +} + +static gboolean +battery_update_os_proc(battery_priv *c) +{ + GString *path; + int len; + GDir *dir; + gboolean ret = FALSE; + const gchar *file; + + ENTER; + c->exist = FALSE; + path = g_string_sized_new(200); + g_string_append(path, PROC_ACPI); + len = path->len; + if (!(dir = g_dir_open(path->str, 0, NULL))) { + DBG("can't open dir %s\n", path->str); + goto out; + } + while (!ret && (file = g_dir_read_name(dir))) { + g_string_append(path, file); + DBG("testing %s\n", path->str); + ret = g_file_test(path->str, G_FILE_TEST_IS_DIR); + if (ret) + ret = read_proc(c, path); + g_string_truncate(path, len); + } + g_dir_close(dir); + +out: + g_string_free(path, TRUE); + RET(ret); +} + +static gboolean +battery_update_os_sys(battery_priv *c) +{ + ENTER; + RET(FALSE); +} + +static gboolean +battery_update_os(battery_priv *c) +{ + ENTER; + RET(battery_update_os_proc(c) || battery_update_os_sys(c)); +} diff --git a/plugins/chart/Makefile b/plugins/chart/Makefile new file mode 100644 index 0000000..cd6849f --- /dev/null +++ b/plugins/chart/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +chart_src = chart.c +chart_cflags = -DPLUGIN $(GTK2_CFLAGS) +chart_libs = $(GTK2_LIBS) +chart_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/chart/chart.c b/plugins/chart/chart.c new file mode 100644 index 0000000..c602c6a --- /dev/null +++ b/plugins/chart/chart.c @@ -0,0 +1,283 @@ +/* + * CPU usage plugin to fbpanel + * + * Copyright (C) 2004 by Alexandre Pereira da Silva + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/*A little bug fixed by Mykola :) */ + + +#include +#include +#include +#include +#include + +#include "plugin.h" +#include "panel.h" +#include "gtkbgbox.h" +#include "chart.h" + + +//#define DEBUGPRN +#include "dbg.h" + + +static void chart_add_tick(chart_priv *c, float *val); +static void chart_draw(chart_priv *c); +static void chart_size_allocate(GtkWidget *widget, GtkAllocation *a, chart_priv *c); +static gint chart_expose_event(GtkWidget *widget, GdkEventExpose *event, chart_priv *c); + +static void chart_alloc_ticks(chart_priv *c); +static void chart_free_ticks(chart_priv *c); +static void chart_alloc_gcs(chart_priv *c, gchar *colors[]); +static void chart_free_gcs(chart_priv *c); + +static void +chart_add_tick(chart_priv *c, float *val) +{ + int i; + + ENTER; + if (!c->ticks) + RET(); + for (i = 0; i < c->rows; i++) { + if (val[i] < 0) + val[i] = 0; + if (val[i] > 1) + val[i] = 1; + c->ticks[i][c->pos] = val[i] * c->h; + DBG("new wval = %uld\n", c->ticks[i][c->pos]); + } + c->pos = (c->pos + 1) % c->w; + gtk_widget_queue_draw(c->da); + + RET(); +} + +static void +chart_draw(chart_priv *c) +{ + int j, i, y; + + ENTER; + if (!c->ticks) + RET(); + for (i = 1; i < c->w-1; i++) { + y = c->h-2; + for (j = 0; j < c->rows; j++) { + int val; + + val = c->ticks[j][(i + c->pos) % c->w]; + if (val) + gdk_draw_line(c->da->window, c->gc_cpu[j], i, y, i, y - val); + y -= val; + } + } + RET(); +} + +static void +chart_size_allocate(GtkWidget *widget, GtkAllocation *a, chart_priv *c) +{ + ENTER; + if (c->w != a->width || c->h != a->height) { + chart_free_ticks(c); + c->w = a->width; + c->h = a->height; + chart_alloc_ticks(c); + c->area.x = 0; + c->area.y = 0; + c->area.width = a->width; + c->area.height = a->height; + if (c->plugin.panel->transparent) { + c->fx = 0; + c->fy = 0; + c->fw = a->width; + c->fh = a->height; + } else if (c->plugin.panel->orientation == GTK_ORIENTATION_HORIZONTAL) { + c->fx = 0; + c->fy = 1; + c->fw = a->width; + c->fh = a->height -2; + } else { + c->fx = 1; + c->fy = 0; + c->fw = a->width -2; + c->fh = a->height; + } + } + gtk_widget_queue_draw(c->da); + RET(); +} + + +static gint +chart_expose_event(GtkWidget *widget, GdkEventExpose *event, chart_priv *c) +{ + ENTER; + gdk_window_clear(widget->window); + chart_draw(c); + + gtk_paint_shadow(widget->style, widget->window, + widget->state, GTK_SHADOW_ETCHED_IN, + &c->area, widget, "frame", c->fx, c->fy, c->fw, c->fh); + +#if 0 + gdk_draw_rectangle(widget->window, + widget->style->bg_gc[GTK_STATE_NORMAL], + FALSE, 0, 0, c->w-1, c->h-1); +#endif + RET(FALSE); +} + +static void +chart_alloc_ticks(chart_priv *c) +{ + int i; + + ENTER; + if (!c->w || !c->rows) + RET(); + c->ticks = g_new0(gint *, c->rows); + for (i = 0; i < c->rows; i++) { + c->ticks[i] = g_new0(gint, c->w); + if (!c->ticks[i]) + DBG2("can't alloc mem: %p %d\n", c->ticks[i], c->w); + } + c->pos = 0; + RET(); +} + + +static void +chart_free_ticks(chart_priv *c) +{ + int i; + + ENTER; + if (!c->ticks) + RET(); + for (i = 0; i < c->rows; i++) + g_free(c->ticks[i]); + g_free(c->ticks); + c->ticks = NULL; + RET(); +} + + +static void +chart_alloc_gcs(chart_priv *c, gchar *colors[]) +{ + int i; + GdkColor color; + + ENTER; + c->gc_cpu = g_new0( typeof(*c->gc_cpu), c->rows); + if (c->gc_cpu) { + for (i = 0; i < c->rows; i++) { + c->gc_cpu[i] = gdk_gc_new(c->plugin.panel->topgwin->window); + gdk_color_parse(colors[i], &color); + gdk_colormap_alloc_color( + gdk_drawable_get_colormap(c->plugin.panel->topgwin->window), + &color, FALSE, TRUE); + gdk_gc_set_foreground(c->gc_cpu[i], &color); + } + } + RET(); +} + + + +static void +chart_free_gcs(chart_priv *c) +{ + int i; + + ENTER; + if (c->gc_cpu) { + for (i = 0; i < c->rows; i++) + g_object_unref(c->gc_cpu[i]); + g_free(c->gc_cpu); + c->gc_cpu = NULL; + } + RET(); +} + + +static void +chart_set_rows(chart_priv *c, int num, gchar *colors[]) +{ + ENTER; + g_assert(num > 0 && num < 10); + chart_free_ticks(c); + chart_free_gcs(c); + c->rows = num; + chart_alloc_ticks(c); + chart_alloc_gcs(c, colors); + RET(); +} + +static int +chart_constructor(plugin_instance *p) +{ + chart_priv *c; + + ENTER; + /* must be allocated by caller */ + c = (chart_priv *) p; + c->rows = 0; + c->ticks = NULL; + c->gc_cpu = NULL; + c->da = p->pwid; + + gtk_widget_set_size_request(c->da, 40, 25); + //gtk_container_set_border_width (GTK_CONTAINER (p->pwid), 1); + g_signal_connect (G_OBJECT (p->pwid), "size-allocate", + G_CALLBACK (chart_size_allocate), (gpointer) c); + + g_signal_connect_after (G_OBJECT (p->pwid), "expose-event", + G_CALLBACK (chart_expose_event), (gpointer) c); + + RET(1); +} + +static void +chart_destructor(plugin_instance *p) +{ + chart_priv *c = (chart_priv *) p; + + ENTER; + chart_free_ticks(c); + chart_free_gcs(c); + RET(); +} + +static chart_class class = { + .plugin = { + .type = "chart", + .name = "Chart", + .description = "Basic chart plugin", + .priv_size = sizeof(chart_priv), + + .constructor = chart_constructor, + .destructor = chart_destructor, + }, + .add_tick = chart_add_tick, + .set_rows = chart_set_rows, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/chart/chart.h b/plugins/chart/chart.h new file mode 100644 index 0000000..44173e1 --- /dev/null +++ b/plugins/chart/chart.h @@ -0,0 +1,29 @@ +#ifndef CHART_H +#define CHART_H + + +#include "plugin.h" +#include "panel.h" + + +/* chart.h */ +typedef struct { + plugin_instance plugin; + GdkGC **gc_cpu; + GtkWidget *da; + + gint **ticks; + gint pos; + gint w, h, rows; + GdkRectangle area; /* frame area and exact positions */ + int fx, fy, fw, fh; +} chart_priv; + +typedef struct { + plugin_class plugin; + void (*add_tick)(chart_priv *c, float *val); + void (*set_rows)(chart_priv *c, int num, gchar *colors[]); +} chart_class; + + +#endif diff --git a/plugins/cpu/Makefile b/plugins/cpu/Makefile new file mode 100644 index 0000000..9df0944 --- /dev/null +++ b/plugins/cpu/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +cpu_src = cpu.c +cpu_cflags = -DPLUGIN $(GTK2_CFLAGS) +cpu_libs = $(GTK2_LIBS) +cpu_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/cpu/cpu.c b/plugins/cpu/cpu.c new file mode 100644 index 0000000..b148ad8 --- /dev/null +++ b/plugins/cpu/cpu.c @@ -0,0 +1,173 @@ + + +/* + * Free BSD support + * A little bug fixed by Mykola :) + * FreeBSD support added by Andreas Wiese + * and was extended by Eygene Ryabinkin + */ + +#include +#include "misc.h" +#include "../chart/chart.h" + +//#define DEBUGPRN +#include "dbg.h" +#if defined(__FreeBSD__) +#include +#include +#include +#include +#endif + +struct cpu_stat { + gulong u, n, s, i, w; // user, nice, system, idle, wait +}; + +typedef struct { + chart_priv chart; + struct cpu_stat cpu_prev; + int timer; + gchar *colors[1]; +} cpu_priv; + +static chart_class *k; + +static void cpu_destructor(plugin_instance *p); + + +#if defined __linux__ +static int +cpu_get_load_real(struct cpu_stat *cpu) +{ + FILE *stat; + + memset(cpu, 0, sizeof(struct cpu_stat)); + stat = fopen("/proc/stat", "r"); + if(!stat) + return -1; + fscanf(stat, "cpu %lu %lu %lu %lu %lu", &cpu->u, &cpu->n, &cpu->s, + &cpu->i, &cpu->w); + fclose(stat); + return 0; +} +#elif defined __FreeBSD__ +static int +cpu_get_load_real(struct cpu_stat *cpu) +{ + static int mib[2] = { -1, -1 }, init = 0; + size_t j; + long ct[CPUSTATES]; + + memset(cpu, 0, sizeof(struct cpu_stat)); + if (init == 0) { + j = 2; + if (sysctlnametomib("kern.cp_time", mib, &j) != 0) + return -1; + + init = 1; + } + + j = sizeof(ct); + if (sysctl(mib, 2, ct, &j, NULL, 0) != 0) + return -1; + cpu->u = ct[CP_USER]; + cpu->n = ct[CP_NICE]; + cpu->s = ct[CP_SYS]; + cpu->i = ct[CP_IDLE]; + cpu->w = 0; + + return 0; +} +#else +static int +cpu_get_load_real(struct cpu_stat *s) +{ + memset(cpu, 0, sizeof(struct cpu_stat)); + return 0; +} +#endif + +static int +cpu_get_load(cpu_priv *c) +{ + gfloat a, b; + struct cpu_stat cpu, cpu_diff; + float total[1]; + gchar buf[40]; + + ENTER; + memset(&cpu, 0, sizeof(cpu)); + memset(&cpu_diff, 0, sizeof(cpu_diff)); + memset(&total, 0, sizeof(total)); + + if (cpu_get_load_real(&cpu)) + goto end; + + cpu_diff.u = cpu.u - c->cpu_prev.u; + cpu_diff.n = cpu.n - c->cpu_prev.n; + cpu_diff.s = cpu.s - c->cpu_prev.s; + cpu_diff.i = cpu.i - c->cpu_prev.i; + cpu_diff.w = cpu.w - c->cpu_prev.w; + c->cpu_prev = cpu; + + a = cpu_diff.u + cpu_diff.n + cpu_diff.s; + b = a + cpu_diff.i + cpu_diff.w; + total[0] = b ? a / b : 1.0; + +end: + DBG("total=%f a=%f b=%f\n", total[0], a, b); + g_snprintf(buf, sizeof(buf), "Cpu: %d%%", (int)(total[0] * 100)); + gtk_widget_set_tooltip_markup(((plugin_instance *)c)->pwid, buf); + k->add_tick(&c->chart, total); + RET(TRUE); + +} + +static int +cpu_constructor(plugin_instance *p) +{ + cpu_priv *c; + + if (!(k = class_get("chart"))) + RET(0); + if (!PLUGIN_CLASS(k)->constructor(p)) + RET(0); + c = (cpu_priv *) p; + c->colors[0] = "green"; + XCG(p->xc, "Color", &c->colors[0], str); + + k->set_rows(&c->chart, 1, c->colors); + gtk_widget_set_tooltip_markup(((plugin_instance *)c)->pwid, "Cpu"); + cpu_get_load(c); + c->timer = g_timeout_add(1000, (GSourceFunc) cpu_get_load, (gpointer) c); + RET(1); +} + + +static void +cpu_destructor(plugin_instance *p) +{ + cpu_priv *c = (cpu_priv *) p; + + ENTER; + g_source_remove(c->timer); + PLUGIN_CLASS(k)->destructor(p); + class_put("chart"); + RET(); +} + + + +static plugin_class class = { + .count = 0, + .type = "cpu", + .name = "Cpu usage", + .version = "1.0", + .description = "Display cpu usage", + .priv_size = sizeof(cpu_priv), + .constructor = cpu_constructor, + .destructor = cpu_destructor, +}; + +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/dclock/Makefile b/plugins/dclock/Makefile new file mode 100644 index 0000000..09f4f9f --- /dev/null +++ b/plugins/dclock/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +dclock_src = dclock.c +dclock_cflags = -DPLUGIN $(GTK2_CFLAGS) +dclock_libs = $(GTK2_LIBS) +dclock_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/dclock/dclock.c b/plugins/dclock/dclock.c new file mode 100644 index 0000000..03fdd5f --- /dev/null +++ b/plugins/dclock/dclock.c @@ -0,0 +1,350 @@ +/* dclock is an adaptation of blueclock by Jochen Baier */ + +#include +#include +#include +#include +#include +#include +#include + + +#include "panel.h" +#include "misc.h" +#include "plugin.h" + +//#define DEBUGPRN +#include "dbg.h" + + +#define TOOLTIP_FMT "%A %x" +#define CLOCK_24H_FMT "%R" +#define CLOCK_12H_FMT "%I:%M" + +#define CLOCK_24H_FMT "%R" +#define CLOCK_24H_SEC_FMT "%T" +#define CLOCK_12H_FMT "%I:%M" +#define CLOCK_12H_SEC_FMT "%I:%M:%S" + +#define COLON_WIDTH 7 +#define COLON_HEIGHT 5 +#define VCOLON_WIDTH 10 +#define VCOLON_HEIGHT 6 +#define DIGIT_WIDTH 11 +#define DIGIT_HEIGHT 15 +#define SHADOW 2 + +#define STR_SIZE 64 + +enum { DC_24H, DC_12H }; + +xconf_enum hours_view_enum[] = { + { .num = DC_24H, .str = "24" }, + { .num = DC_12H, .str = "12" }, + { .num = 0, .str = NULL }, +}; + +typedef struct +{ + plugin_instance plugin; + GtkWidget *main; + GtkWidget *calendar_window; + gchar *tfmt, tstr[STR_SIZE]; + gchar *cfmt, cstr[STR_SIZE]; + char *action; + int timer; + GdkPixbuf *glyphs; //vert row of '0'-'9' and ':' + GdkPixbuf *clock; + guint32 color; + gboolean show_seconds; + gboolean hours_view; + GtkOrientation orientation; +} dclock_priv; + +//static dclock_priv me; + +static GtkWidget * +dclock_create_calendar() +{ + GtkWidget *calendar, *win; + + win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size(GTK_WINDOW(win), 180, 180); + gtk_window_set_decorated(GTK_WINDOW(win), FALSE); + gtk_window_set_resizable(GTK_WINDOW(win), FALSE); + gtk_container_set_border_width(GTK_CONTAINER(win), 5); + gtk_window_set_skip_taskbar_hint(GTK_WINDOW(win), TRUE); + gtk_window_set_skip_pager_hint(GTK_WINDOW(win), TRUE); + gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_MOUSE); + gtk_window_set_title(GTK_WINDOW(win), "calendar"); + gtk_window_stick(GTK_WINDOW(win)); + + calendar = gtk_calendar_new(); + gtk_calendar_display_options( + GTK_CALENDAR(calendar), + GTK_CALENDAR_SHOW_WEEK_NUMBERS | GTK_CALENDAR_SHOW_DAY_NAMES + | GTK_CALENDAR_SHOW_HEADING); + gtk_container_add(GTK_CONTAINER(win), GTK_WIDGET(calendar)); + + return win; +} + +static gboolean +clicked(GtkWidget *widget, GdkEventButton *event, dclock_priv *dc) +{ + ENTER; + if (event->type == GDK_BUTTON_PRESS && event->button == 3 + && event->state & GDK_CONTROL_MASK) + { + RET(FALSE); + } + if (dc->action != NULL) + g_spawn_command_line_async(dc->action, NULL); + else + { + if (dc->calendar_window == NULL) + { + dc->calendar_window = dclock_create_calendar(); + gtk_widget_show_all(dc->calendar_window); + gtk_widget_set_tooltip_markup(dc->plugin.pwid, NULL); + } + else + { + gtk_widget_destroy(dc->calendar_window); + dc->calendar_window = NULL; + } + } + RET(TRUE); +} + +static gint +clock_update(dclock_priv *dc) +{ + char output[STR_SIZE], *tmp, *utf8; + time_t now; + struct tm * detail; + int i, x, y; + + ENTER; + time(&now); + detail = localtime(&now); + + if (!strftime(output, sizeof(output), dc->cfmt, detail)) + strcpy(output, " : "); + if (strcmp(dc->cstr, output)) + { + strncpy(dc->cstr, output, sizeof(dc->cstr)); + x = y = SHADOW; + for (tmp = output; *tmp; tmp++) + { + DBGE("%c", *tmp); + if (isdigit(*tmp)) + { + i = *tmp - '0'; + gdk_pixbuf_copy_area(dc->glyphs, i * 20, 0, + DIGIT_WIDTH, DIGIT_HEIGHT, + dc->clock, x, y); + x += DIGIT_WIDTH; + } + else if (*tmp == ':') + { + if (dc->orientation == GTK_ORIENTATION_HORIZONTAL) { + gdk_pixbuf_copy_area(dc->glyphs, 10 * 20, 0, + COLON_WIDTH, DIGIT_HEIGHT - 2, + dc->clock, x, y + 2); + x += COLON_WIDTH; + } else { + x = SHADOW; + y += DIGIT_HEIGHT; + gdk_pixbuf_copy_area(dc->glyphs, 10 * 20, 0, + VCOLON_WIDTH, VCOLON_HEIGHT, + dc->clock, x + DIGIT_WIDTH / 2, y); + y += VCOLON_HEIGHT; + } + } + else + { + ERR("dclock: got %c while expecting for digit or ':'\n", *tmp); + } + } + DBG("\n"); + gtk_widget_queue_draw(dc->main); + } + + if (dc->calendar_window || !strftime(output, sizeof(output), + dc->tfmt, detail)) + output[0] = 0; + if (strcmp(dc->tstr, output)) + { + strcpy(dc->tstr, output); + if (dc->tstr[0] && (utf8 = g_locale_to_utf8(output, -1, + NULL, NULL, NULL))) + { + gtk_widget_set_tooltip_markup(dc->plugin.pwid, utf8); + g_free(utf8); + } + else + gtk_widget_set_tooltip_markup(dc->plugin.pwid, NULL); + } + RET(TRUE); +} + +static void +dclock_set_color(GdkPixbuf *glyphs, guint32 color) +{ + guchar *p1, *p2; + int w, h; + guint r, g, b; + + ENTER; + p1 = gdk_pixbuf_get_pixels(glyphs); + h = gdk_pixbuf_get_height(glyphs); + r = (color & 0x00ff0000) >> 16; + g = (color & 0x0000ff00) >> 8; + b = (color & 0x000000ff); + DBG("%dx%d: %02x %02x %02x\n", + gdk_pixbuf_get_width(glyphs), gdk_pixbuf_get_height(glyphs), r, g, b); + while (h--) + { + for (p2 = p1, w = gdk_pixbuf_get_width(glyphs); w; w--, p2 += 4) + { + DBG("here %02x %02x %02x %02x\n", p2[0], p2[1], p2[2], p2[3]); + if (p2[3] == 0 || !(p2[0] || p2[1] || p2[2])) + continue; + p2[0] = r; + p2[1] = g; + p2[2] = b; + } + p1 += gdk_pixbuf_get_rowstride(glyphs); + } + DBG("here\n"); + RET(); +} + +static void +dclock_create_pixbufs(dclock_priv *dc) +{ + int width, height; + GdkPixbuf *ch, *cv; + + ENTER; + width = height = SHADOW; + width += COLON_WIDTH + 4 * DIGIT_WIDTH; + height += DIGIT_HEIGHT; + if (dc->show_seconds) + width += COLON_WIDTH + 2 * DIGIT_WIDTH; + if (dc->orientation == GTK_ORIENTATION_VERTICAL) { + DBG("width=%d height=%d aw=%d\n", width, height, dc->plugin.panel->aw); + if (width < dc->plugin.panel->aw) { + dc->orientation = GTK_ORIENTATION_HORIZONTAL; + goto done; + } + width = height = SHADOW; + ch = gdk_pixbuf_new_subpixbuf(dc->glyphs, 200, 0, 8, 8); + cv = gdk_pixbuf_rotate_simple(ch, 270); + gdk_pixbuf_copy_area(cv, 0, 0, 8, 8, ch, 0, 0); + g_object_unref(cv); + g_object_unref(ch); + height += DIGIT_HEIGHT * 2 + VCOLON_HEIGHT; + width += DIGIT_WIDTH * 2; + if (dc->show_seconds) + height += VCOLON_HEIGHT + DIGIT_HEIGHT; + } +done: + dc->clock = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height); + DBG("width=%d height=%d\n", width, height); + gdk_pixbuf_fill(dc->clock, 0); + RET(); +} + +static void +dclock_destructor(plugin_instance *p) +{ + dclock_priv *dc = (dclock_priv *)p; + + ENTER; + if (dc->timer) + g_source_remove(dc->timer); + gtk_widget_destroy(dc->main); + RET(); +} + +static int +dclock_constructor(plugin_instance *p) +{ + gchar *color_str; + dclock_priv *dc; + //int width; + + ENTER; + DBG("dclock: use 'tclock' plugin for text version of a time and date\n"); + dc = (dclock_priv *) p; + dc->glyphs = gdk_pixbuf_new_from_file(IMGPREFIX "/dclock_glyphs.png", NULL); + if (!dc->glyphs) + RET(0); + + dc->cfmt = NULL; + dc->tfmt = TOOLTIP_FMT; + dc->action = NULL; + dc->color = 0xff000000; + dc->show_seconds = FALSE; + dc->hours_view = DC_24H; + dc->orientation = p->panel->orientation; + color_str = NULL; + XCG(p->xc, "TooltipFmt", &dc->tfmt, str); + XCG(p->xc, "ClockFmt", &dc->cfmt, str); + XCG(p->xc, "ShowSeconds", &dc->show_seconds, enum, bool_enum); + XCG(p->xc, "HoursView", &dc->hours_view, enum, hours_view_enum); + XCG(p->xc, "Action", &dc->action, str); + XCG(p->xc, "Color", &color_str, str); + if (dc->cfmt) + { + ERR("dclock: ClockFmt option is deprecated. Please use\n" + "following options instead\n" + " ShowSeconds = false | true\n" + " HoursView = 12 | 24\n"); + xconf_del(xconf_get(p->xc, "ClockFmt"), FALSE); + dc->cfmt = NULL; + } + if (color_str) + { + GdkColor color; + if (gdk_color_parse (color_str, &color)) + dc->color = gcolor2rgb24(&color); + } + if (dc->hours_view == DC_24H) + dc->cfmt = (dc->show_seconds) ? CLOCK_24H_SEC_FMT : CLOCK_24H_FMT; + else + dc->cfmt = (dc->show_seconds) ? CLOCK_12H_SEC_FMT : CLOCK_12H_FMT; + dclock_create_pixbufs(dc); + if (dc->color != 0xff000000) + dclock_set_color(dc->glyphs, dc->color); + + dc->main = gtk_image_new_from_pixbuf(dc->clock); + gtk_misc_set_alignment(GTK_MISC(dc->main), 0.5, 0.5); + gtk_misc_set_padding(GTK_MISC(dc->main), 1, 1); + gtk_container_add(GTK_CONTAINER(p->pwid), dc->main); + //gtk_widget_show(dc->clockw); + g_signal_connect (G_OBJECT (p->pwid), "button_press_event", + G_CALLBACK (clicked), (gpointer) dc); + gtk_widget_show_all(dc->main); + dc->timer = g_timeout_add(1000, (GSourceFunc) clock_update, (gpointer)dc); + clock_update(dc); + + RET(1); +} + + +static plugin_class class = { + .fname = NULL, + .count = 0, + .type = "dclock", + .name = "Digital Clock", + .version = "1.0", + .description = "Digital clock with tooltip", + .priv_size = sizeof(dclock_priv), + + .constructor = dclock_constructor, + .destructor = dclock_destructor, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/deskno/Makefile b/plugins/deskno/Makefile new file mode 100644 index 0000000..badd30c --- /dev/null +++ b/plugins/deskno/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +deskno_src = deskno.c +deskno_cflags = -DPLUGIN $(GTK2_CFLAGS) +deskno_libs = $(GTK2_LIBS) +deskno_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/deskno/deskno.c b/plugins/deskno/deskno.c new file mode 100644 index 0000000..3d4a715 --- /dev/null +++ b/plugins/deskno/deskno.c @@ -0,0 +1,123 @@ +// reused dclock.c and variables from pager.c +// 11/23/04 by cmeury@users.sf.net", + +#include +#include +#include + +#include "panel.h" +#include "misc.h" +#include "plugin.h" + +//#define DEBUGPRN +#include "dbg.h" + +typedef struct { + plugin_instance plugin; + GtkWidget *main; + GtkWidget *namew; + int deskno, desknum; +} deskno_priv; + +static void +change_desktop(deskno_priv *dc, int delta) +{ + int newdesk = dc->deskno + delta; + + ENTER; + if (newdesk < 0) + newdesk = dc->desknum - 1; + else if (newdesk >= dc->desknum) + newdesk = 0; + DBG("%d/%d -> %d\n", dc->deskno, dc->desknum, newdesk); + Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, newdesk, 0, 0, 0, 0); + RET(); +} + +static void +clicked(GtkWidget *widget, deskno_priv *dc) +{ + ENTER; + change_desktop(dc, 1); +} + +static gboolean +scrolled(GtkWidget *widget, GdkEventScroll *event, deskno_priv *dc) +{ + ENTER; + change_desktop(dc, (event->direction == GDK_SCROLL_UP + || event->direction == GDK_SCROLL_LEFT) ? -1 : 1); + return FALSE; +} + +static gint +name_update(GtkWidget *widget, deskno_priv *dc) +{ + char buffer [15]; + + ENTER; + dc->deskno = get_net_current_desktop(); + sprintf(buffer, "%d", dc->deskno + 1); + gtk_label_set_markup(GTK_LABEL(dc->namew), buffer); + RET(TRUE); +} + +static gint +update(GtkWidget *widget, deskno_priv *dc) +{ + ENTER; + dc->desknum = get_net_number_of_desktops(); + RET(TRUE); +} + +static int +deskno_constructor(plugin_instance *p) +{ + deskno_priv *dc; + + ENTER; + dc = (deskno_priv *) p; + dc->main = gtk_button_new(); + gtk_button_set_relief(GTK_BUTTON(dc->main), GTK_RELIEF_NONE); + g_signal_connect(G_OBJECT(dc->main), "clicked", G_CALLBACK(clicked), + (gpointer) dc); + g_signal_connect(G_OBJECT(dc->main), "scroll-event", G_CALLBACK(scrolled), + (gpointer) dc); + dc->namew = gtk_label_new("ww"); + gtk_container_add(GTK_CONTAINER(dc->main), dc->namew); + gtk_container_add(GTK_CONTAINER(p->pwid), dc->main); + //gtk_widget_add_events(p->pwid, GDK_SCROLL_MASK); + //gtk_widget_add_events(dc->main, GDK_SCROLL_MASK); + gtk_widget_show_all(p->pwid); + name_update(dc->main, dc); + update(dc->main, dc); + g_signal_connect(G_OBJECT(fbev), "current_desktop", G_CALLBACK + (name_update), (gpointer) dc); + g_signal_connect(G_OBJECT(fbev), "number_of_desktops", G_CALLBACK + (update), (gpointer) dc); + RET(1); +} + + +static void +deskno_destructor(plugin_instance *p) +{ + deskno_priv *dc = (deskno_priv *) p; + + ENTER; + g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), name_update, dc); + g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), update, dc); + RET(); +} + +static plugin_class class = { + .type = "deskno", + .name = "Desktop No v1", + .version = "0.6", + .description = "Display workspace number", + .priv_size = sizeof(deskno_priv), + + .constructor = deskno_constructor, + .destructor = deskno_destructor, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/deskno2/#Makefile# b/plugins/deskno2/#Makefile# new file mode 100644 index 0000000..6c497db --- /dev/null +++ b/plugins/deskno2/#Makefile# @@ -0,0 +1,6 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/deskno2/Makefile b/plugins/deskno2/Makefile new file mode 100644 index 0000000..9fb05eb --- /dev/null +++ b/plugins/deskno2/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +deskno2_src = deskno2.c +deskno2_cflags = -DPLUGIN $(GTK2_CFLAGS) +deskno2_libs = $(GTK2_LIBS) +deskno2_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/deskno2/deskno2.c b/plugins/deskno2/deskno2.c new file mode 100644 index 0000000..213a041 --- /dev/null +++ b/plugins/deskno2/deskno2.c @@ -0,0 +1,134 @@ +/* Display workspace number, by cmeury@users.sf.net */ + +#include +#include +#include + +#include "panel.h" +#include "misc.h" +#include "plugin.h" + +//#define DEBUGPRN +#include "dbg.h" + +typedef struct { + plugin_instance plugin; + GtkWidget *main; + int dno; // current desktop nomer + int dnum; // number of desktops + char **dnames; // desktop names + int dnames_num; // number of desktop names + char **lnames; // label names + char *fmt; +} deskno_priv; + +static void +clicked(GtkWidget *widget, deskno_priv *dc) +{ + system("xfce-setting-show workspaces"); +} + +static void +update_dno(GtkWidget *widget, deskno_priv *dc) +{ + ENTER; + dc->dno = fb_ev_current_desktop(fbev); + gtk_button_set_label(GTK_BUTTON(dc->main), dc->lnames[dc->dno]); + + RET(); +} + +static void +update_all(GtkWidget *widget, deskno_priv *dc) +{ + int i; + + ENTER; + dc->dnum = fb_ev_number_of_desktops(fbev); + if (dc->dnames) + g_strfreev (dc->dnames); + if (dc->lnames) + g_strfreev (dc->lnames); + dc->dnames = get_utf8_property_list(GDK_ROOT_WINDOW(), a_NET_DESKTOP_NAMES, &(dc->dnames_num)); + dc->lnames = g_new0 (gchar*, dc->dnum + 1); + for (i = 0; i < MIN(dc->dnum, dc->dnames_num); i++) { + dc->lnames[i] = g_strdup(dc->dnames[i]); + } + for (; i < dc->dnum; i++) { + dc->lnames[i] = g_strdup_printf("%d", i + 1); + } + update_dno(widget, dc); + RET(); +} + + +static gboolean +scroll (GtkWidget *widget, GdkEventScroll *event, deskno_priv *dc) +{ + int dno; + + ENTER; + dno = dc->dno + ((event->direction == GDK_SCROLL_UP) ? (-1) : (+1)); + if (dno < 0) + dno = dc->dnum - 1; + else if (dno == dc->dnum) + dno = 0; + Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, dno, 0, 0, 0, 0); + RET(TRUE); + +} + +static int +deskno_constructor(plugin_instance *p) +{ + deskno_priv *dc; + ENTER; + dc = (deskno_priv *) p; + dc->main = gtk_button_new_with_label("w"); + gtk_button_set_relief(GTK_BUTTON(dc->main),GTK_RELIEF_NONE); + gtk_container_set_border_width(GTK_CONTAINER(dc->main), 0); + //gtk_button_set_alignment(GTK_BUTTON(dc->main), 0, 0.5); + g_signal_connect(G_OBJECT(dc->main), "clicked", G_CALLBACK (clicked), (gpointer) dc); + g_signal_connect(G_OBJECT(dc->main), "scroll-event", G_CALLBACK(scroll), (gpointer) dc); + + update_all(dc->main, dc); + + gtk_container_add(GTK_CONTAINER(p->pwid), dc->main); + gtk_widget_show_all(p->pwid); + + g_signal_connect (G_OBJECT (fbev), "current_desktop", G_CALLBACK (update_dno), (gpointer) dc); + g_signal_connect (G_OBJECT (fbev), "desktop_names", G_CALLBACK (update_all), (gpointer) dc); + g_signal_connect (G_OBJECT (fbev), "number_of_desktops", G_CALLBACK (update_all), (gpointer) dc); + + RET(1); +} + + +static void +deskno_destructor(plugin_instance *p) +{ + deskno_priv *dc = (deskno_priv *) p; + + ENTER; + /* disconnect ALL handlers matching func and data */ + g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), update_dno, dc); + g_signal_handlers_disconnect_by_func(G_OBJECT(fbev), update_all, dc); + if (dc->dnames) + g_strfreev(dc->dnames); + if (dc->lnames) + g_strfreev(dc->lnames); + RET(); +} + +static plugin_class class = { + .count = 0, + .type = "deskno2", + .name = "Desktop No v2", + .version = "0.6", + .description = "Display workspace number", + .priv_size = sizeof(deskno_priv), + + .constructor = deskno_constructor, + .destructor = deskno_destructor, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/genmon/Makefile b/plugins/genmon/Makefile new file mode 100644 index 0000000..29bf002 --- /dev/null +++ b/plugins/genmon/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +genmon_src = genmon.c +genmon_cflags = -DPLUGIN $(GTK2_CFLAGS) +genmon_libs = $(GTK2_LIBS) +genmon_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/genmon/genmon.c b/plugins/genmon/genmon.c new file mode 100644 index 0000000..7f3ef30 --- /dev/null +++ b/plugins/genmon/genmon.c @@ -0,0 +1,120 @@ +/* genmon_priv.c -- Generic monitor plugin for fbpanel + * + * Copyright (C) 2007 Davide Truffa + * + * This plugin is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include + +#include "panel.h" +#include "misc.h" +#include "plugin.h" + +//#define DEBUG +#include "dbg.h" + +#define FMT "%s" + +typedef struct { + plugin_instance plugin; + int time; + int timer; + int max_text_len; + char *command; + char *textsize; + char *textcolor; + GtkWidget *main; +} genmon_priv; + +static int +text_update(genmon_priv *gm) +{ + FILE *fp; + char text[256]; + char *markup; + int len; + + ENTER; + fp = popen(gm->command, "r"); + fgets(text, sizeof(text), fp); + pclose(fp); + len = strlen(text) - 1; + if (len >= 0) { + if (text[len] == '\n') + text[len] = 0; + + markup = g_markup_printf_escaped(FMT, gm->textsize, gm->textcolor, + text); + gtk_label_set_markup (GTK_LABEL(gm->main), markup); + g_free(markup); + } + RET(TRUE); +} + +static void +genmon_destructor(plugin_instance *p) +{ + genmon_priv *gm = (genmon_priv *) p; + + ENTER; + if (gm->timer) { + g_source_remove(gm->timer); + } + RET(); +} + +static int +genmon_constructor(plugin_instance *p) +{ + genmon_priv *gm; + + ENTER; + gm = (genmon_priv *) p; + gm->command = "date +%R"; + gm->time = 1; + gm->textsize = "medium"; + gm->textcolor = "darkblue"; + gm->max_text_len = 30; + + XCG(p->xc, "Command", &gm->command, str); + XCG(p->xc, "TextSize", &gm->textsize, str); + XCG(p->xc, "TextColor", &gm->textcolor, str); + XCG(p->xc, "PollingTime", &gm->time, int); + XCG(p->xc, "MaxTextLength", &gm->max_text_len, int); + + gm->main = gtk_label_new(NULL); + gtk_label_set_max_width_chars(GTK_LABEL(gm->main), gm->max_text_len); + text_update(gm); + gtk_container_set_border_width (GTK_CONTAINER (p->pwid), 1); + gtk_container_add(GTK_CONTAINER(p->pwid), gm->main); + gtk_widget_show_all(p->pwid); + gm->timer = g_timeout_add((guint) gm->time * 1000, + (GSourceFunc) text_update, (gpointer) gm); + + RET(1); +} + + +static plugin_class class = { + .count = 0, + .type = "genmon", + .name = "Generic Monitor", + .version = "0.3", + .description = "Display the output of a program/script into the panel", + .priv_size = sizeof(genmon_priv), + + .constructor = genmon_constructor, + .destructor = genmon_destructor, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/icons/Makefile b/plugins/icons/Makefile new file mode 100644 index 0000000..b7a7c79 --- /dev/null +++ b/plugins/icons/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +icons_src = icons.c +icons_cflags = -DPLUGIN $(GTK2_CFLAGS) +icons_libs = $(GTK2_LIBS) +icons_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/icons/icons.c b/plugins/icons/icons.c new file mode 100644 index 0000000..f6f3d38 --- /dev/null +++ b/plugins/icons/icons.c @@ -0,0 +1,539 @@ +#include +#include +#include +#include + +#include +#include +//#include + +#include +#include +#include + + + +#include "panel.h" +#include "misc.h" +#include "plugin.h" + + +//#define DEBUGPRN +#include "dbg.h" + +typedef struct wmpix_t { + struct wmpix_t *next; + gulong *data; + int size; + XClassHint ch; +} wmpix_t; + +struct _icons; +typedef struct _task{ + struct _icons *ics; + Window win; + int refcount; + XClassHint ch; +} task; + +typedef struct _icons{ + plugin_instance plugin; + Window *wins; + int win_num; + GHashTable *task_list; + int num_tasks; + wmpix_t *wmpix; + wmpix_t *dicon; +} icons_priv; + +static void ics_propertynotify(icons_priv *ics, XEvent *ev); +static GdkFilterReturn ics_event_filter( XEvent *, GdkEvent *, icons_priv *); +static void icons_destructor(plugin_instance *p); + +/******************************************/ +/* Resource Release Code */ +/******************************************/ +static void +free_task(icons_priv *ics, task *tk, int hdel) +{ + ENTER; + ics->num_tasks--; + if (hdel) + g_hash_table_remove(ics->task_list, &tk->win); + if (tk->ch.res_class) + XFree(tk->ch.res_class); + if (tk->ch.res_name) + XFree(tk->ch.res_name); + g_free(tk); + RET(); +} + +static gboolean +task_remove_every(Window *win, task *tk) +{ + free_task(tk->ics, tk, 0); + return TRUE; +} + + +static void +drop_config(icons_priv *ics) +{ + wmpix_t *wp; + + ENTER; + /* free application icons */ + while (ics->wmpix) + { + wp = ics->wmpix; + ics->wmpix = ics->wmpix->next; + g_free(wp->ch.res_name); + g_free(wp->ch.res_class); + g_free(wp->data); + g_free(wp); + } + + /* free default icon */ + if (ics->dicon) + { + g_free(ics->dicon->data); + g_free(ics->dicon); + ics->dicon = NULL; + } + + /* free task list */ + g_hash_table_foreach_remove(ics->task_list, + (GHRFunc) task_remove_every, (gpointer)ics); + + if (ics->wins) + { + DBG("free ics->wins\n"); + XFree(ics->wins); + ics->wins = NULL; + } + RET(); +} + +static void +get_wmclass(task *tk) +{ + ENTER; + if (tk->ch.res_name) + XFree(tk->ch.res_name); + if (tk->ch.res_class) + XFree(tk->ch.res_class); + if (!XGetClassHint (gdk_display, tk->win, &tk->ch)) + tk->ch.res_class = tk->ch.res_name = NULL; + DBG("name=%s class=%s\n", tk->ch.res_name, tk->ch.res_class); + RET(); +} + + + + +static inline task * +find_task (icons_priv * ics, Window win) +{ + ENTER; + RET(g_hash_table_lookup(ics->task_list, &win)); +} + + +static int task_has_icon(task *tk) +{ + XWMHints *hints; + gulong *data; + int n; + + ENTER; + data = get_xaproperty(tk->win, a_NET_WM_ICON, XA_CARDINAL, &n); + if (data) + { + XFree(data); + RET(1); + } + + hints = XGetWMHints(GDK_DISPLAY(), tk->win); + if (hints) + { + if ((hints->flags & IconPixmapHint) || (hints->flags & IconMaskHint)) + { + XFree (hints); + RET(1); + } + XFree (hints); + } + RET(0); +} + +static wmpix_t * +get_user_icon(icons_priv *ics, task *tk) +{ + wmpix_t *tmp; + int mc, mn; + + ENTER; + if (!(tk->ch.res_class || tk->ch.res_name)) + RET(NULL); + DBG("\nch.res_class=[%s] ch.res_name=[%s]\n", tk->ch.res_class, + tk->ch.res_name); + + for (tmp = ics->wmpix; tmp; tmp = tmp->next) + { + DBG("tmp.res_class=[%s] tmp.res_name=[%s]\n", tmp->ch.res_class, + tmp->ch.res_name); + mc = !tmp->ch.res_class || !strcmp(tmp->ch.res_class, tk->ch.res_class); + mn = !tmp->ch.res_name || !strcmp(tmp->ch.res_name, tk->ch.res_name); + DBG("mc=%d mn=%d\n", mc, mn); + if (mc && mn) + { + DBG("match !!!!\n"); + RET(tmp); + } + } + RET(NULL); +} + + + +gulong * +pixbuf2argb (GdkPixbuf *pixbuf, int *size) +{ + gulong *data; + guchar *pixels; + gulong *p; + gint width, height, stride; + gint x, y; + gint n_channels; + + ENTER; + *size = 0; + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + stride = gdk_pixbuf_get_rowstride (pixbuf); + n_channels = gdk_pixbuf_get_n_channels (pixbuf); + + *size += 2 + width * height; + p = data = g_malloc (*size * sizeof (gulong)); + *p++ = width; + *p++ = height; + + pixels = gdk_pixbuf_get_pixels (pixbuf); + + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + { + guchar r, g, b, a; + + r = pixels[y*stride + x*n_channels + 0]; + g = pixels[y*stride + x*n_channels + 1]; + b = pixels[y*stride + x*n_channels + 2]; + if (n_channels >= 4) + a = pixels[y*stride + x*n_channels + 3]; + else + a = 255; + + *p++ = a << 24 | r << 16 | g << 8 | b ; + } + } + RET(data); +} + + + +static void +set_icon_maybe (icons_priv *ics, task *tk) +{ + wmpix_t *pix; + + ENTER; + g_assert ((ics != NULL) && (tk != NULL)); + g_return_if_fail(tk != NULL); + + + pix = get_user_icon(ics, tk); + if (!pix) + { + if (task_has_icon(tk)) + RET(); + pix = ics->dicon; + } + if (!pix) + RET(); + + DBG("%s size=%d\n", pix->ch.res_name, pix->size); + XChangeProperty (GDK_DISPLAY(), tk->win, + a_NET_WM_ICON, XA_CARDINAL, 32, PropModeReplace, (guchar*) pix->data, pix->size); + + RET(); +} + + + +/* tell to remove element with zero refcount */ +static gboolean +task_remove_stale(Window *win, task *tk) +{ + ENTER; + if (tk->refcount-- == 0) + { + free_task(tk->ics, tk, 0); + RET(TRUE); + } + RET(FALSE); +} + +/***************************************************** + * handlers for NET actions * + *****************************************************/ + +static GdkFilterReturn +ics_event_filter( XEvent *xev, GdkEvent *event, icons_priv *ics) +{ + + ENTER; + g_assert(ics != NULL); + if (xev->type == PropertyNotify) + ics_propertynotify(ics, xev); + RET(GDK_FILTER_CONTINUE); +} + + +static void +do_net_client_list(icons_priv *ics) +{ + int i; + task *tk; + + ENTER; + if (ics->wins) + { + DBG("free ics->wins\n"); + XFree(ics->wins); + ics->wins = NULL; + } + ics->wins = get_xaproperty (GDK_ROOT_WINDOW(), + a_NET_CLIENT_LIST, XA_WINDOW, &ics->win_num); + if (!ics->wins) + RET(); + DBG("alloc ics->wins\n"); + for (i = 0; i < ics->win_num; i++) + { + if ((tk = g_hash_table_lookup(ics->task_list, &ics->wins[i]))) + { + tk->refcount++; + } + else + { + tk = g_new0(task, 1); + tk->refcount++; + ics->num_tasks++; + tk->win = ics->wins[i]; + tk->ics = ics; + + if (!FBPANEL_WIN(tk->win)) + { + XSelectInput(GDK_DISPLAY(), tk->win, + PropertyChangeMask | StructureNotifyMask); + } + get_wmclass(tk); + set_icon_maybe(ics, tk); + g_hash_table_insert(ics->task_list, &tk->win, tk); + } + } + + /* remove windows that arn't in the NET_CLIENT_LIST anymore */ + g_hash_table_foreach_remove(ics->task_list, + (GHRFunc) task_remove_stale, NULL); + RET(); +} + +static void +ics_propertynotify(icons_priv *ics, XEvent *ev) +{ + Atom at; + Window win; + + + ENTER; + win = ev->xproperty.window; + at = ev->xproperty.atom; + DBG("win=%lx at=%ld\n", win, at); + if (win != GDK_ROOT_WINDOW()) + { + task *tk = find_task(ics, win); + + if (!tk) + RET(); + if (at == XA_WM_CLASS) + { + get_wmclass(tk); + set_icon_maybe(ics, tk); + } + else if (at == XA_WM_HINTS) + { + set_icon_maybe(ics, tk); + } + } + RET(); +} + + +static int +read_application(icons_priv *ics, xconf *xc) +{ + GdkPixbuf *gp = NULL; + + gchar *fname, *iname, *appname, *classname; + wmpix_t *wp = NULL; + gulong *data; + int size; + + ENTER; + iname = fname = appname = classname = NULL; + XCG(xc, "image", &fname, str); + XCG(xc, "icon", &iname, str); + XCG(xc, "appname", &appname, str); + XCG(xc, "classname", &classname, str); + fname = expand_tilda(fname); + + DBG("appname=%s classname=%s\n", appname, classname); + if (!(fname || iname)) + goto error; + gp = fb_pixbuf_new(iname, fname, 48, 48, FALSE); + if (gp) + { + if ((data = pixbuf2argb(gp, &size))) + { + wp = g_new0 (wmpix_t, 1); + wp->next = ics->wmpix; + wp->data = data; + wp->size = size; + wp->ch.res_name = g_strdup(appname); + wp->ch.res_class = g_strdup(classname); + DBG("read name=[%s] class=[%s]\n", + wp->ch.res_name, wp->ch.res_class); + ics->wmpix = wp; + } + g_object_unref(gp); + } + g_free(fname); + RET(1); + +error: + g_free(fname); + RET(0); +} + +static int +read_dicon(icons_priv *ics, gchar *name) +{ + gchar *fname; + GdkPixbuf *gp; + int size; + gulong *data; + + ENTER; + fname = expand_tilda(name); + if (!fname) + RET(0); + gp = gdk_pixbuf_new_from_file(fname, NULL); + if (gp) + { + if ((data = pixbuf2argb(gp, &size))) + { + ics->dicon = g_new0 (wmpix_t, 1); + ics->dicon->data = data; + ics->dicon->size = size; + } + g_object_unref(gp); + } + g_free(fname); + RET(1); +} + + +static int +ics_parse_config(icons_priv *ics) +{ + gchar *def_icon; + plugin_instance *p = (plugin_instance *) ics; + int i; + xconf *pxc; + + ENTER; + def_icon = NULL; + XCG(p->xc, "defaulticon", &def_icon, str); + if (def_icon && !read_dicon(ics, def_icon)) + goto error; + + for (i = 0; (pxc = xconf_find(p->xc, "application", i)); i++) + if (!read_application(ics, pxc)) + goto error; + RET(1); + +error: + RET(0); +} + +static void +theme_changed(icons_priv *ics) +{ + ENTER; + drop_config(ics); + ics_parse_config(ics); + do_net_client_list(ics); + RET(); +} + +static int +icons_constructor(plugin_instance *p) +{ + icons_priv *ics; + + ENTER; + ics = (icons_priv *) p; + ics->task_list = g_hash_table_new(g_int_hash, g_int_equal); + theme_changed(ics); + g_signal_connect_swapped(G_OBJECT(gtk_icon_theme_get_default()), + "changed", (GCallback) theme_changed, ics); + g_signal_connect_swapped(G_OBJECT (fbev), "client_list", + G_CALLBACK (do_net_client_list), (gpointer) ics); + gdk_window_add_filter(NULL, (GdkFilterFunc)ics_event_filter, ics ); + + RET(1); +} + + +static void +icons_destructor(plugin_instance *p) +{ + icons_priv *ics = (icons_priv *) p; + + ENTER; + g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), do_net_client_list, + ics); + g_signal_handlers_disconnect_by_func(G_OBJECT(gtk_icon_theme_get_default()), + theme_changed, ics); + gdk_window_remove_filter(NULL, (GdkFilterFunc)ics_event_filter, ics ); + drop_config(ics); + g_hash_table_destroy(ics->task_list); + RET(); +} + +static plugin_class class = { + .count = 0, + .invisible = 1, + + .type = "icons", + .name = "Icons", + .version = "1.0", + .description = "Invisible plugin to change window icons", + .priv_size = sizeof(icons_priv), + + + .constructor = icons_constructor, + .destructor = icons_destructor, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/image/Makefile b/plugins/image/Makefile new file mode 100644 index 0000000..8407136 --- /dev/null +++ b/plugins/image/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +image_src = image.c +image_cflags = -DPLUGIN $(GTK2_CFLAGS) +image_libs = $(GTK2_LIBS) +image_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/image/image.c b/plugins/image/image.c new file mode 100644 index 0000000..5a6cbe0 --- /dev/null +++ b/plugins/image/image.c @@ -0,0 +1,100 @@ +#include + +#include + +#include "panel.h" +#include "misc.h" +#include "plugin.h" + +//#define DEBUGPRN +#include "dbg.h" + + +typedef struct { + plugin_instance plugin; + GdkPixmap *pix; + GdkBitmap *mask; + GtkWidget *mainw; +} image_priv; + + + +static void +image_destructor(plugin_instance *p) +{ + image_priv *img = (image_priv *) p; + + ENTER; + gtk_widget_destroy(img->mainw); + if (img->mask) + g_object_unref(img->mask); + if (img->pix) + g_object_unref(img->pix); + RET(); +} + +static int +image_constructor(plugin_instance *p) +{ + gchar *tooltip, *fname; + image_priv *img; + GdkPixbuf *gp, *gps; + GtkWidget *wid; + GError *err = NULL; + + ENTER; + img = (image_priv *) p; + tooltip = fname = 0; + XCG(p->xc, "image", &fname, str); + XCG(p->xc, "tooltip", &tooltip, str); + fname = expand_tilda(fname); + + img->mainw = gtk_event_box_new(); + gtk_widget_show(img->mainw); + //g_signal_connect(G_OBJECT(img->mainw), "expose_event", + // G_CALLBACK(gtk_widget_queue_draw), NULL); + gp = gdk_pixbuf_new_from_file(fname, &err); + if (!gp) { + g_warning("image: can't read image %s\n", fname); + wid = gtk_label_new("?"); + } else { + float ratio; + + ratio = (p->panel->orientation == GTK_ORIENTATION_HORIZONTAL) ? + (float) (p->panel->ah - 2) / (float) gdk_pixbuf_get_height(gp) + : (float) (p->panel->aw - 2) / (float) gdk_pixbuf_get_width(gp); + gps = gdk_pixbuf_scale_simple (gp, + ratio * ((float) gdk_pixbuf_get_width(gp)), + ratio * ((float) gdk_pixbuf_get_height(gp)), + GDK_INTERP_HYPER); + gdk_pixbuf_render_pixmap_and_mask(gps, &img->pix, &img->mask, 127); + gdk_pixbuf_unref(gp); + gdk_pixbuf_unref(gps); + wid = gtk_image_new_from_pixmap(img->pix, img->mask); + + } + gtk_widget_show(wid); + gtk_container_add(GTK_CONTAINER(img->mainw), wid); + gtk_container_set_border_width(GTK_CONTAINER(img->mainw), 0); + g_free(fname); + gtk_container_add(GTK_CONTAINER(p->pwid), img->mainw); + if (tooltip) { + gtk_widget_set_tooltip_markup(img->mainw, tooltip); + g_free(tooltip); + } + RET(1); +} + + +static plugin_class class = { + .count = 0, + .type = "image", + .name = "Show Image", + .version = "1.0", + .description = "Dispaly Image and Tooltip", + .priv_size = sizeof(image_priv), + + .constructor = image_constructor, + .destructor = image_destructor, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/launchbar/Makefile b/plugins/launchbar/Makefile new file mode 100644 index 0000000..27e0c63 --- /dev/null +++ b/plugins/launchbar/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +launchbar_src = launchbar.c +launchbar_cflags = -DPLUGIN $(GTK2_CFLAGS) +launchbar_libs = $(GTK2_LIBS) +launchbar_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/launchbar/launchbar.c b/plugins/launchbar/launchbar.c new file mode 100644 index 0000000..c9c0dc4 --- /dev/null +++ b/plugins/launchbar/launchbar.c @@ -0,0 +1,298 @@ + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include + +#include "panel.h" +#include "misc.h" +#include "plugin.h" +#include "gtkbgbox.h" +#include "run.h" +#include "gtkbar.h" + +//#define DEBUGPRN +#include "dbg.h" + + +typedef enum { + CURSOR_STANDARD, + CURSOR_DND +} CursorType; + +enum { + TARGET_URILIST, + TARGET_MOZ_URL, + TARGET_UTF8_STRING, + TARGET_STRING, + TARGET_TEXT, + TARGET_COMPOUND_TEXT +}; + +static const GtkTargetEntry target_table[] = { + { "text/uri-list", 0, TARGET_URILIST}, + { "text/x-moz-url", 0, TARGET_MOZ_URL}, + { "UTF8_STRING", 0, TARGET_UTF8_STRING }, + { "COMPOUND_TEXT", 0, 0 }, + { "TEXT", 0, 0 }, + { "STRING", 0, 0 } +}; + +struct launchbarb; + +typedef struct btn { + //GtkWidget *button, *pixmap; + struct launchbar_priv *lb; + gchar *action; +} btn; + +#define MAXBUTTONS 20 +typedef struct launchbar_priv { + plugin_instance plugin; + GtkWidget *box; + btn btns[MAXBUTTONS]; + int btn_num; + int iconsize; + unsigned int discard_release_event : 1; +} launchbar_priv; + + +static gboolean +my_button_pressed(GtkWidget *widget, GdkEventButton *event, btn *b ) +{ + ENTER; + if (event->type == GDK_BUTTON_PRESS && event->button == 3 + && event->state & GDK_CONTROL_MASK) + { + b->lb->discard_release_event = 1; + RET(FALSE); + } + if (event->type == GDK_BUTTON_RELEASE && b->lb->discard_release_event) + { + b->lb->discard_release_event = 0; + RET(TRUE); + } + g_assert(b != NULL); + if (event->type == GDK_BUTTON_RELEASE) + { + if ((event->x >=0 && event->x < widget->allocation.width) + && (event->y >=0 && event->y < widget->allocation.height)) + { + run_app(b->action); + } + } + RET(TRUE); +} + +static void +launchbar_destructor(plugin_instance *p) +{ + launchbar_priv *lb = (launchbar_priv *) p; + int i; + + ENTER; + gtk_widget_destroy(lb->box); + for (i = 0; i < lb->btn_num; i++) + g_free(lb->btns[i].action); + + RET(); +} + + +static void +drag_data_received_cb (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *sd, + guint info, + guint time, + btn *b) +{ + gchar *s, *str, *tmp, *tok, *tok2; + + ENTER; + if (sd->length <= 0) + RET(); + DBG("uri drag received: info=%d/%s len=%d data=%s\n", + info, target_table[info].target, sd->length, sd->data); + if (info == TARGET_URILIST) + { + /* white-space separated list of uri's */ + s = g_strdup((gchar *)sd->data); + str = g_strdup(b->action); + for (tok = strtok(s, "\n \t\r"); tok; tok = strtok(NULL, "\n \t\r")) + { + tok2 = g_filename_from_uri(tok, NULL, NULL); + /* if conversion to filename was ok, use it, otherwise + * lets append original uri */ + tmp = g_strdup_printf("%s '%s'", str, tok2 ? tok2 : tok); + g_free(str); + g_free(tok2); + str = tmp; + } + DBG("cmd=<%s>\n", str); + g_spawn_command_line_async(str, NULL); + g_free(str); + g_free(s); + } + else if (info == TARGET_MOZ_URL) + { + gchar *utf8, *tmp; + + utf8 = g_utf16_to_utf8((gunichar2 *) sd->data, (glong) sd->length, + NULL, NULL, NULL); + tmp = utf8 ? strchr(utf8, '\n') : NULL; + if (!tmp) + { + ERR("Invalid UTF16 from text/x-moz-url target"); + g_free(utf8); + gtk_drag_finish(context, FALSE, FALSE, time); + RET(); + } + *tmp = '\0'; + tmp = g_strdup_printf("%s %s", b->action, utf8); + g_spawn_command_line_async(tmp, NULL); + DBG("%s %s\n", b->action, utf8); + g_free(utf8); + g_free(tmp); + } + RET(); +} + +static int +read_button(plugin_instance *p, xconf *xc) +{ + launchbar_priv *lb = (launchbar_priv *) p; + gchar *iname, *fname, *tooltip, *action; + GtkWidget *button; + + ENTER; + if (lb->btn_num >= MAXBUTTONS) + { + ERR("launchbar: max number of buttons (%d) was reached." + "skipping the rest\n", lb->btn_num ); + RET(0); + } + iname = tooltip = fname = action = NULL; + XCG(xc, "image", &fname, str); + XCG(xc, "icon", &iname, str); + XCG(xc, "action", &action, str); + XCG(xc, "tooltip", &tooltip, str); + + action = expand_tilda(action); + fname = expand_tilda(fname); + + button = fb_button_new(iname, fname, lb->iconsize, + lb->iconsize, 0x202020, NULL); + + g_signal_connect (G_OBJECT (button), "button-release-event", + G_CALLBACK (my_button_pressed), (gpointer) &lb->btns[lb->btn_num]); + g_signal_connect (G_OBJECT (button), "button-press-event", + G_CALLBACK (my_button_pressed), (gpointer) &lb->btns[lb->btn_num]); + + GTK_WIDGET_UNSET_FLAGS (button, GTK_CAN_FOCUS); + // DnD support + gtk_drag_dest_set (GTK_WIDGET(button), + GTK_DEST_DEFAULT_ALL, //GTK_DEST_DEFAULT_HIGHLIGHT, + target_table, G_N_ELEMENTS (target_table), + GDK_ACTION_COPY); + g_signal_connect (G_OBJECT(button), "drag_data_received", + G_CALLBACK (drag_data_received_cb), + (gpointer) &lb->btns[lb->btn_num]); + + gtk_box_pack_start(GTK_BOX(lb->box), button, FALSE, FALSE, 0); + gtk_widget_show(button); + + if (p->panel->transparent) + gtk_bgbox_set_background(button, BG_INHERIT, + p->panel->tintcolor, p->panel->alpha); + gtk_widget_set_tooltip_markup(button, tooltip); + + g_free(fname); + //g_free(iname); + DBG("here\n"); + + lb->btns[lb->btn_num].action = action; + lb->btns[lb->btn_num].lb = lb; + lb->btn_num++; + + RET(1); +} + +static void +launchbar_size_alloc(GtkWidget *widget, GtkAllocation *a, + launchbar_priv *lb) +{ + int dim; + + ENTER; + if (lb->plugin.panel->orientation == GTK_ORIENTATION_HORIZONTAL) + dim = a->height / lb->iconsize; + else + dim = a->width / lb->iconsize; + DBG("width=%d height=%d iconsize=%d -> dim=%d\n", + a->width, a->height, lb->iconsize, dim); + gtk_bar_set_dimension(GTK_BAR(lb->box), dim); + RET(); +} + +static int +launchbar_constructor(plugin_instance *p) +{ + launchbar_priv *lb; + int i; + xconf *pxc; + GtkWidget *ali; + static gchar *launchbar_rc = "style 'launchbar-style'\n" + "{\n" + "GtkWidget::focus-line-width = 0\n" + "GtkWidget::focus-padding = 0\n" + "GtkButton::default-border = { 0, 0, 0, 0 }\n" + "GtkButton::default-outside-border = { 0, 0, 0, 0 }\n" + "}\n" + "widget '*' style 'launchbar-style'"; + + ENTER; + lb = (launchbar_priv *) p; + lb->iconsize = p->panel->max_elem_height; + DBG("iconsize=%d\n", lb->iconsize); + + gtk_widget_set_name(p->pwid, "launchbar"); + gtk_rc_parse_string(launchbar_rc); + //get_button_spacing(&req, GTK_CONTAINER(p->pwid), ""); + ali = gtk_alignment_new(0.5, 0.5, 0, 0); + g_signal_connect(G_OBJECT(ali), "size-allocate", + (GCallback) launchbar_size_alloc, lb); + gtk_container_set_border_width(GTK_CONTAINER(ali), 0); + gtk_container_add(GTK_CONTAINER(p->pwid), ali); + lb->box = gtk_bar_new(p->panel->orientation, 0, + lb->iconsize, lb->iconsize); + gtk_container_add(GTK_CONTAINER(ali), lb->box); + gtk_container_set_border_width(GTK_CONTAINER (lb->box), 0); + gtk_widget_show_all(ali); + + for (i = 0; (pxc = xconf_find(p->xc, "button", i)); i++) + read_button(p, pxc); + RET(1); +} + +static plugin_class class = { + .count = 0, + .type = "launchbar", + .name = "Launchbar", + .version = "1.0", + .description = "Bar with application launchers", + .priv_size = sizeof(launchbar_priv), + + .constructor = launchbar_constructor, + .destructor = launchbar_destructor, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/mem/Makefile b/plugins/mem/Makefile new file mode 100644 index 0000000..17965a8 --- /dev/null +++ b/plugins/mem/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +mem_src = mem.c +mem_cflags = -DPLUGIN $(GTK2_CFLAGS) +mem_libs = $(GTK2_LIBS) +mem_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/mem/mem.c b/plugins/mem/mem.c new file mode 100644 index 0000000..d55febf --- /dev/null +++ b/plugins/mem/mem.c @@ -0,0 +1,221 @@ +#include +#include +#include +#include +#include +#include +#include + + +#include "panel.h" +#include "misc.h" +#include "plugin.h" + +//#define DEBUGPRN +#include "dbg.h" + + +typedef struct +{ + plugin_instance plugin; + GtkWidget *mem_pb; + GtkWidget *swap_pb; + GtkWidget *box; + int timer; + int show_swap; +} mem_priv; + +typedef struct +{ + char *name; + gulong val; + int valid; +} mem_type_t; + +typedef struct +{ + struct + { + gulong total; + gulong used; + } mem; + struct + { + gulong total; + gulong used; + } swap; +} stats_t; + +static stats_t stats; + +#if defined __linux__ +#undef MT_ADD +#define MT_ADD(x) MT_ ## x, +enum { +#include "mt.h" + MT_NUM +}; + +#undef MT_ADD +#define MT_ADD(x) { #x, 0, 0 }, +mem_type_t mt[] = +{ +#include "mt.h" +}; + +static gboolean +mt_match(char *buf, mem_type_t *m) +{ + gulong val; + int len; + + len = strlen(m->name); + if (strncmp(buf, m->name, len)) + return FALSE; + if (sscanf(buf + len + 1, "%lu", &val) != 1) + return FALSE; + m->val = val; + m->valid = 1; + DBG("%s: %lu\n", m->name, val); + return TRUE; +} + +static void +mem_usage() +{ + FILE *fp; + char buf[160]; + int i; + + fp = fopen("/proc/meminfo", "r"); + if (!fp) + return; + for (i = 0; i < MT_NUM; i++) + { + mt[i].valid = 0; + mt[i].val = 0; + } + + while ((fgets(buf, sizeof(buf), fp)) != NULL) + { + for (i = 0; i < MT_NUM; i++) + { + if (!mt[i].valid && mt_match(buf, mt + i)) + break; + } + } + fclose(fp); + + stats.mem.total = mt[MT_MemTotal].val; + stats.mem.used = mt[MT_MemTotal].val -(mt[MT_MemFree].val + + mt[MT_Buffers].val + mt[MT_Cached].val + mt[MT_Slab].val); + stats.swap.total = mt[MT_SwapTotal].val; + stats.swap.used = mt[MT_SwapTotal].val - mt[MT_SwapFree].val; +} +#else +static void +mem_usage() +{ + +} +#endif + +static gboolean +mem_update(mem_priv *mem) +{ + gdouble mu, su; + char str[90]; + + ENTER; + mu = su = 0; + bzero(&stats, sizeof(stats)); + mem_usage(); + if (stats.mem.total) + mu = (gdouble) stats.mem.used / (gdouble) stats.mem.total; + if (stats.swap.total) + su = (gdouble) stats.swap.used / (gdouble) stats.swap.total; + g_snprintf(str, sizeof(str), + "Mem: %d%%, %lu MB of %lu MB\n" + "Swap: %d%%, %lu MB of %lu MB", + (int)(mu * 100), stats.mem.used >> 10, stats.mem.total >> 10, + (int)(su * 100), stats.swap.used >> 10, stats.swap.total >> 10); + DBG("%s\n", str); + gtk_widget_set_tooltip_markup(mem->plugin.pwid, str); + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(mem->mem_pb), mu); + if (mem->show_swap) + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(mem->swap_pb), su); + RET(TRUE); +} + + +static void +mem_destructor(plugin_instance *p) +{ + mem_priv *mem = (mem_priv *)p; + + ENTER; + if (mem->timer) + g_source_remove(mem->timer); + gtk_widget_destroy(mem->box); + RET(); +} + +static int +mem_constructor(plugin_instance *p) +{ + mem_priv *mem; + gint w, h; + GtkProgressBarOrientation o; + + ENTER; + mem = (mem_priv *) p; + XCG(p->xc, "ShowSwap", &mem->show_swap, enum, bool_enum); + mem->box = p->panel->my_box_new(FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (mem->box), 0); + + if (p->panel->orientation == GTK_ORIENTATION_HORIZONTAL) + { + o = GTK_PROGRESS_BOTTOM_TO_TOP; + w = 9; + h = 0; + } + else + { + o = GTK_PROGRESS_LEFT_TO_RIGHT; + w = 0; + h = 9; + } + mem->mem_pb = gtk_progress_bar_new(); + gtk_box_pack_start(GTK_BOX(mem->box), mem->mem_pb, FALSE, FALSE, 0); + gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(mem->mem_pb), o); + gtk_widget_set_size_request(mem->mem_pb, w, h); + + if (mem->show_swap) + { + mem->swap_pb = gtk_progress_bar_new(); + gtk_box_pack_start(GTK_BOX(mem->box), mem->swap_pb, FALSE, FALSE, 0); + gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(mem->swap_pb), o); + gtk_widget_set_size_request(mem->swap_pb, w, h); + } + + gtk_widget_show_all(mem->box); + gtk_container_add(GTK_CONTAINER(p->pwid), mem->box); + gtk_widget_set_tooltip_markup(mem->plugin.pwid, "XXX"); + mem_update(mem); + mem->timer = g_timeout_add(3000, (GSourceFunc) mem_update, (gpointer)mem); + RET(1); +} + +static plugin_class class = { + .fname = NULL, + .count = 0, + .type = "mem", + .name = "Memory Monitor", + .version = "1.0", + .description = "Show memory usage", + .priv_size = sizeof(mem_priv), + + .constructor = mem_constructor, + .destructor = mem_destructor, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/mem/mt.h b/plugins/mem/mt.h new file mode 100644 index 0000000..6b4b797 --- /dev/null +++ b/plugins/mem/mt.h @@ -0,0 +1,11 @@ + +/* Memory types (MT) to scan in /proc/meminfo */ +MT_ADD(MemTotal) +MT_ADD(MemFree) +MT_ADD(MemShared) +MT_ADD(Slab) +MT_ADD(Buffers) +MT_ADD(Cached) + +MT_ADD(SwapTotal) +MT_ADD(SwapFree) diff --git a/plugins/mem2/Makefile b/plugins/mem2/Makefile new file mode 100644 index 0000000..c828dd0 --- /dev/null +++ b/plugins/mem2/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +mem2_src = mem2.c +mem2_cflags = -DPLUGIN $(GTK2_CFLAGS) +mem2_libs = $(GTK2_LIBS) +mem2_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/mem2/mem2.c b/plugins/mem2/mem2.c new file mode 100644 index 0000000..3e4f329 --- /dev/null +++ b/plugins/mem2/mem2.c @@ -0,0 +1,177 @@ +/* + * mem usage plugin to fbpanel + * + * Licence: GPLv2 + * + * bercik-rrp@users.sf.net + */ + +#include "../chart/chart.h" +#include +#include + +//#define DEBUGPRN +#include "dbg.h" + +#define CHECK_PERIOD 2 /* second */ + +typedef struct { + chart_priv chart; + int timer; + gulong max; + gchar *colors[2]; +} mem2_priv; + +static chart_class *k; + +static void mem2_destructor(plugin_instance *p); + +typedef struct { + char *name; + gulong val; + int valid; +} mem_type_t; + + +#if defined __linux__ +#undef MT_ADD +#define MT_ADD(x) MT_ ## x, +enum { +#include "../mem/mt.h" + MT_NUM +}; + +#undef MT_ADD +#define MT_ADD(x) { #x, 0, 0 }, +mem_type_t mt[] = +{ +#include "../mem/mt.h" +}; + +static gboolean +mt_match(char *buf, mem_type_t *m) +{ + gulong val; + int len; + + len = strlen(m->name); + if (strncmp(buf, m->name, len)) + return FALSE; + if (sscanf(buf + len + 1, "%lu", &val) != 1) + return FALSE; + m->val = val; + m->valid = 1; + DBG("%s: %lu\n", m->name, val); + return TRUE; +} + +static int +mem_usage(mem2_priv *c) +{ + FILE *fp; + char buf[160]; + long unsigned int total[2]; + float total_r[2]; + int i; + + fp = fopen("/proc/meminfo", "r"); + if (!fp) + RET(FALSE);; + for (i = 0; i < MT_NUM; i++) + { + mt[i].valid = 0; + mt[i].val = 0; + } + + while ((fgets(buf, sizeof(buf), fp)) != NULL) + { + for (i = 0; i < MT_NUM; i++) + { + if (!mt[i].valid && mt_match(buf, mt + i)) + break; + } + } + fclose(fp); + + total[0] = (float)(mt[MT_MemTotal].val -(mt[MT_MemFree].val + + mt[MT_Buffers].val + mt[MT_Cached].val + mt[MT_Slab].val)); + total[1] = (float)(mt[MT_SwapTotal].val - mt[MT_SwapFree].val); + total_r[0] = (float)total[0] / mt[MT_MemTotal].val; + total_r[1] = (float)total[1] / mt[MT_SwapTotal].val; + + g_snprintf(buf, sizeof(buf), + "Mem: %d%%, %lu MB of %lu MB\n" + "Swap: %d%%, %lu MB of %lu MB", + (int)(total_r[0] * 100), total[0] >> 10, mt[MT_MemTotal].val >> 10, + (int)(total_r[1] * 100), total[1] >> 10, mt[MT_SwapTotal].val >> 10); + + k->add_tick(&c->chart, total_r); + gtk_widget_set_tooltip_markup(((plugin_instance *)c)->pwid, buf); + RET(TRUE); + +} +#else +static int +mem_usage() +{ + +} +#endif + +static int +mem2_constructor(plugin_instance *p) +{ + mem2_priv *c; + + if (!(k = class_get("chart"))) + RET(0); + if (!PLUGIN_CLASS(k)->constructor(p)) + RET(0); + c = (mem2_priv *) p; + + c->colors[0] = "red"; + c->colors[1] = NULL; + XCG(p->xc, "MemColor", &c->colors[0], str); + XCG(p->xc, "SwapColor", &c->colors[1], str); + + if (c->colors[1] == NULL) { + k->set_rows(&c->chart, 1, c->colors); + } else { + k->set_rows(&c->chart, 2, c->colors); + } + gtk_widget_set_tooltip_markup(((plugin_instance *)c)->pwid, + "Memory"); + mem_usage(c); + c->timer = g_timeout_add(CHECK_PERIOD * 1000, + (GSourceFunc) mem_usage, (gpointer) c); + RET(1); +} + + +static void +mem2_destructor(plugin_instance *p) +{ + mem2_priv *c = (mem2_priv *) p; + + ENTER; + if (c->timer) + g_source_remove(c->timer); + PLUGIN_CLASS(k)->destructor(p); + class_put("chart"); + RET(); +} + + +static plugin_class class = { + .fname = NULL, + .count = 0, + .type = "mem2", + .name = "Chart Memory Monitor", + .version = "1.0", + .description = "Show memory usage as chart", + .priv_size = sizeof(mem2_priv), + + .constructor = mem2_constructor, + .destructor = mem2_destructor, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/menu/Makefile b/plugins/menu/Makefile new file mode 100644 index 0000000..29f7f83 --- /dev/null +++ b/plugins/menu/Makefile @@ -0,0 +1,11 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +menu_src = menu.c \ + system_menu.c +menu_cflags = -DPLUGIN $(GTK2_CFLAGS) +menu_libs = $(GTK2_LIBS) +menu_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/menu/menu.c b/plugins/menu/menu.c new file mode 100644 index 0000000..ef1c4fe --- /dev/null +++ b/plugins/menu/menu.c @@ -0,0 +1,390 @@ +#include +#include + +#include +#include +#include + +#include "panel.h" +#include "misc.h" +#include "plugin.h" +#include "bg.h" +#include "gtkbgbox.h" +#include "run.h" + +//#define DEBUGPRN +#include "dbg.h" + +#define MENU_DEFAULT_ICON_SIZE 22 + +typedef struct { + plugin_instance plugin; + GtkWidget *menu, *bg; + int iconsize, paneliconsize; + xconf *xc; + guint tout, rtout; + gboolean has_system_menu; + time_t btime; + gint icon_size; +} menu_priv; + +xconf *xconf_new_from_systemmenu(); +gboolean systemmenu_changed(time_t btime); +static void menu_create(plugin_instance *p); +static void menu_destroy(menu_priv *m); +static gboolean check_system_menu(plugin_instance *p); + +/* Copies original config while replacing specific entries + * with autogenerated configs */ +static xconf * +menu_expand_xc(xconf *xc, menu_priv *m) +{ + xconf *nxc, *cxc, *smenu_xc; + GSList *w; + + ENTER; + if (!xc) + RET(NULL); + nxc = xconf_new(xc->name, xc->value); + DBG("new node:%s\n", nxc->name); + for (w = xc->sons; w; w = g_slist_next(w)) + { + cxc = w->data; + if (!strcmp(cxc->name, "systemmenu")) + { + smenu_xc = xconf_new_from_systemmenu(); + xconf_append_sons(nxc, smenu_xc); + xconf_del(smenu_xc, FALSE); + m->has_system_menu = TRUE; + continue; + } + if (!strcmp(cxc->name, "include")) + { + smenu_xc = xconf_new_from_file(cxc->value, "include"); + xconf_append_sons(nxc, smenu_xc); + xconf_del(smenu_xc, FALSE); + continue; + } + xconf_append(nxc, menu_expand_xc(cxc, m)); + } + return nxc; +} + +#if 0 +/* XXX: should be global service with following API + * register_command, unregister_command, run_command + */ +static void +run_command(GtkWidget *widget, void (*cmd)(void)) +{ + ENTER; + cmd(); + RET(); +} +#endif + +static GtkWidget * +menu_create_separator() +{ + return gtk_separator_menu_item_new(); +} + +/* Creates menu item. Text and image are read from xconf. Action + * depends on @menu. If @menu is NULL, action is to execute external + * command. Otherwise it is to pop up @menu menu */ +static GtkWidget * +menu_create_item(xconf *xc, GtkWidget *menu, menu_priv *m) +{ + gchar *name, *fname, *iname, *action, *cmd; + GtkWidget *mi; + + cmd = name = fname = action = iname = NULL; + XCG(xc, "name", &name, str); + mi = gtk_image_menu_item_new_with_label(name ? name : ""); + gtk_container_set_border_width(GTK_CONTAINER(mi), 0); + XCG(xc, "image", &fname, str); + fname = expand_tilda(fname); + XCG(xc, "icon", &iname, str); + if (fname || iname) + { + GdkPixbuf *pb; + + if ((pb = fb_pixbuf_new(iname, fname, m->icon_size, m->icon_size, + FALSE))) + { + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), + gtk_image_new_from_pixbuf(pb)); + g_object_unref(G_OBJECT(pb)); + } + } + g_free(fname); + + if (menu) + { + gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu); + goto done; + } + XCG(xc, "action", &action, str); + if (action) + { + action = expand_tilda(action); + + g_signal_connect_swapped(G_OBJECT(mi), "activate", + (GCallback)run_app, action); + g_object_set_data_full(G_OBJECT(mi), "activate", + action, g_free); + goto done; + } + XCG(xc, "command", &cmd, str); + if (cmd) + { + /* XXX: implement command API */ +#if 0 + command *tmp; + + for (tmp = commands; tmp->name; tmp++) + if (!g_ascii_strcasecmp(cmd, tmp->name)) + { + g_signal_connect(G_OBJECT(mi), "activate", + (GCallback)run_command, tmp->cmd); + goto done; + } +#endif + } + +done: + return mi; +} + +/* Creates menu and optionally button to pop it up. + * If @ret_menu is TRUE, then a menu is returned. Otherwise, + * button is created, linked to a menu and returned instead. */ +static GtkWidget * +menu_create_menu(xconf *xc, gboolean ret_menu, menu_priv *m) +{ + GtkWidget *mi, *menu; + GSList *w; + xconf *nxc; + + if (!xc) + return NULL; + menu = gtk_menu_new (); + gtk_container_set_border_width(GTK_CONTAINER(menu), 0); + for (w = xc->sons; w ; w = g_slist_next(w)) + { + nxc = w->data; + if (!strcmp(nxc->name, "separator")) + mi = menu_create_separator(); + else if (!strcmp(nxc->name, "item")) + mi = menu_create_item(nxc, NULL, m); + else if (!strcmp(nxc->name, "menu")) + mi = menu_create_menu(nxc, FALSE, m); + else + continue; + gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); + } + gtk_widget_show_all(menu); + return (ret_menu) ? menu : menu_create_item(xc, menu, m); +} + +static gboolean +menu_unmap(GtkWidget *menu, plugin_instance *p) +{ + ENTER; + if (p->panel->autohide) + ah_start(p->panel); + RET(FALSE); +} + +static void +menu_create(plugin_instance *p) +{ + menu_priv *m = (menu_priv *) p; + + ENTER; + if (m->menu) + menu_destroy(m); + m->xc = menu_expand_xc(p->xc, m); + m->menu = menu_create_menu(m->xc, TRUE, m); + g_signal_connect(G_OBJECT(m->menu), "unmap", + G_CALLBACK(menu_unmap), p); + m->btime = time(NULL); + if (m->has_system_menu) + m->tout = g_timeout_add(30000, (GSourceFunc) check_system_menu, p); + RET(); +} + +static void +menu_destroy(menu_priv *m) +{ + ENTER; + if (m->menu) { + gtk_widget_destroy(m->menu); + m->menu = NULL; + m->has_system_menu = FALSE; + } + if (m->tout) { + g_source_remove(m->tout); + m->tout = 0; + } + if (m->rtout) { + g_source_remove(m->rtout); + m->rtout = 0; + } + if (m->xc) { + xconf_del(m->xc, FALSE); + m->xc = NULL; + } + RET(); +} + +static gboolean +my_button_pressed(GtkWidget *widget, GdkEventButton *event, plugin_instance *p) +{ + menu_priv *m = (menu_priv *) p; + + ENTER; + /* propagate Control-Button3 to the panel */ + if (event->type == GDK_BUTTON_PRESS && event->button == 3 + && event->state & GDK_CONTROL_MASK) + { + RET(FALSE); + } + + if ((event->type == GDK_BUTTON_PRESS) + && (event->x >=0 && event->x < widget->allocation.width) + && (event->y >=0 && event->y < widget->allocation.height)) + { + if (!m->menu) + menu_create(p); + if (p->panel->autohide) + ah_stop(p->panel); + gtk_menu_popup(GTK_MENU(m->menu), + NULL, NULL, (GtkMenuPositionFunc)menu_pos, widget, + event->button, event->time); + + } + RET(TRUE); +} + + +static void +make_button(plugin_instance *p, xconf *xc) +{ + int w, h; + menu_priv *m; + gchar *fname, *iname; + + ENTER; + m = (menu_priv *) p; + /* XXX: this code is duplicated in every plugin. + * Lets run it once in a panel */ + if (p->panel->orientation == GTK_ORIENTATION_HORIZONTAL) + { + w = -1; + h = p->panel->max_elem_height; + } + else + { + w = p->panel->max_elem_height; + h = -1; + } + fname = iname = NULL; + XCG(xc, "image", &fname, str); + fname = expand_tilda(fname); + XCG(xc, "icon", &iname, str); + if (fname || iname) + { + m->bg = fb_button_new(iname, fname, w, h, 0x702020, NULL); + gtk_container_add(GTK_CONTAINER(p->pwid), m->bg); + if (p->panel->transparent) + gtk_bgbox_set_background(m->bg, BG_INHERIT, 0, 0); + g_signal_connect (G_OBJECT (m->bg), "button-press-event", + G_CALLBACK (my_button_pressed), p); + } + g_free(fname); +} + +static gboolean +rebuild_menu(plugin_instance *p) +{ + menu_priv *m = (menu_priv *) p; + + ENTER; + if (m->menu && GTK_WIDGET_MAPPED(m->menu)) + RET(TRUE); + menu_create(p); + m->rtout = 0; + RET(FALSE); +} + +static void +schedule_rebuild_menu(plugin_instance *p) +{ + menu_priv *m = (menu_priv *) p; + + ENTER; + if (!m->rtout) { + DBG("scheduling menu rebuild p=%p\n", p); + m->rtout = g_timeout_add(2000, (GSourceFunc) rebuild_menu, p); + } + RET(); + +} + +static gboolean +check_system_menu(plugin_instance *p) +{ + menu_priv *m = (menu_priv *) p; + + ENTER; + if (systemmenu_changed(m->btime)) + schedule_rebuild_menu(p); + + RET(TRUE); +} + +static int +menu_constructor(plugin_instance *p) +{ + menu_priv *m; + + ENTER; + m = (menu_priv *) p; + m->icon_size = MENU_DEFAULT_ICON_SIZE; + XCG(p->xc, "iconsize", &m->icon_size, int); + DBG("icon_size=%d\n", m->icon_size); + make_button(p, p->xc); + g_signal_connect_swapped(G_OBJECT(icon_theme), + "changed", (GCallback) schedule_rebuild_menu, p); + schedule_rebuild_menu(p); + RET(1); +} + + +static void +menu_destructor(plugin_instance *p) +{ + menu_priv *m = (menu_priv *) p; + + ENTER; + g_signal_handlers_disconnect_by_func(G_OBJECT(icon_theme), + schedule_rebuild_menu, p); + menu_destroy(m); + RET(); +} + + +static plugin_class class = { + .count = 0, + .type = "menu", + .name = "Menu", + .version = "1.0", + .description = "Menu", + .priv_size = sizeof(menu_priv), + + .constructor = menu_constructor, + .destructor = menu_destructor, +}; + +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/menu/system_menu.c b/plugins/menu/system_menu.c new file mode 100644 index 0000000..1b93b32 --- /dev/null +++ b/plugins/menu/system_menu.c @@ -0,0 +1,338 @@ + + +#include +#include +#include +#include + +#include "panel.h" +#include "xconf.h" + +//#define DEBUGPRN +#include "dbg.h" + +static const char desktop_ent[] = "Desktop Entry"; +static const gchar app_dir_name[] = "applications"; + +typedef struct { + gchar *name; + gchar *icon; + gchar *local_name; +} cat_info; + +static cat_info main_cats[] = { + { "AudioVideo", "applications-multimedia", c_("Audio & Video") }, + { "Education", "applications-other", c_("Education") }, + { "Game", "applications-games", c_("Game") }, + { "Graphics", "applications-graphics", c_("Graphics") }, + { "Network", "applications-internet", c_("Network") }, + { "Office", "applications-office", c_("Office") }, + { "Settings", "preferences-system", c_("Settings") }, + { "System", "applications-system", c_("System") }, + { "Utility", "applications-utilities", c_("Utilities") }, + { "Development","applications-development", c_("Development") }, +}; + +static void +do_app_file(GHashTable *ht, const gchar *file) +{ + GKeyFile *f; + gchar *name, *icon, *action,*dot; + gchar **cats, **tmp; + xconf *ixc, *vxc, *mxc; + + ENTER; + DBG("desktop: %s\n", file); + /* get values */ + name = icon = action = dot = NULL; + cats = tmp = NULL; + f = g_key_file_new(); + if (!g_key_file_load_from_file(f, file, 0, NULL)) + goto out; + if (g_key_file_get_boolean(f, desktop_ent, "NoDisplay", NULL)) + { + DBG("\tNoDisplay\n"); + goto out; + } + if (g_key_file_has_key(f, desktop_ent, "OnlyShowIn", NULL)) + { + DBG("\tOnlyShowIn\n"); + goto out; + } + if (!(action = g_key_file_get_string(f, desktop_ent, "Exec", NULL))) + { + DBG("\tNo Exec\n"); + goto out; + } + if (!(cats = g_key_file_get_string_list(f, + desktop_ent, "Categories", NULL, NULL))) + { + DBG("\tNo Categories\n"); + goto out; + } + if (!(name = g_key_file_get_locale_string(f, + desktop_ent, "Name", NULL, NULL))) + { + DBG("\tNo Name\n"); + goto out; + } + icon = g_key_file_get_string(f, desktop_ent, "Icon", NULL); + if (!icon) + DBG("\tNo Icon\n"); + + /* ignore program arguments */ + while ((dot = strchr(action, '%'))) { + if (dot[1] != '\0') + dot[0] = dot[1] = ' '; + } + DBG("action: %s\n", action); + /* if icon is NOT an absolute path but has an extention, + * e.g. firefox.png, then drop an extenstion to allow to load it + * as themable icon */ + if (icon && icon[0] != '/' && (dot = strrchr(icon, '.' )) && + !(strcasecmp(dot + 1, "png") && strcasecmp(dot + 1, "svg"))) + { + *dot = '\0'; + } + DBG("icon: %s\n", icon); + + for (mxc = NULL, tmp = cats; *tmp; tmp++) + if ((mxc = g_hash_table_lookup(ht, *tmp))) + break; + if (!mxc) + { + DBG("\tUnknown categories\n"); + goto out; + } + + ixc = xconf_new("item", NULL); + xconf_append(mxc, ixc); + if (icon) + { + vxc = xconf_new((icon[0] == '/') ? "image" : "icon", icon); + xconf_append(ixc, vxc); + } + vxc = xconf_new("name", name); + xconf_append(ixc, vxc); + vxc = xconf_new("action", action); + xconf_append(ixc, vxc); + +out: + g_free(icon); + g_free(name); + g_free(action); + g_strfreev(cats); + g_key_file_free(f); +} + +static void +do_app_dir_real(GHashTable *ht, const gchar *dir) +{ + GDir *d = NULL; + gchar *cwd; + const gchar *name; + + ENTER; + DBG("%s\n", dir); + cwd = g_get_current_dir(); + if (g_chdir(dir)) + { + DBG("can't chdir to %s\n", dir); + goto out; + } + if (!(d = g_dir_open(".", 0, NULL))) + { + ERR("can't open dir %s\n", dir); + goto out; + } + + while ((name = g_dir_read_name(d))) + { + if (g_file_test(name, G_FILE_TEST_IS_DIR)) + { + do_app_dir_real(ht, name); + continue; + } + if (!g_str_has_suffix(name, ".desktop")) + continue; + do_app_file(ht, name); + } + +out: + if (d) + g_dir_close(d); + g_chdir(cwd); + g_free(cwd); + RET(); +} + +static void +do_app_dir(GHashTable *ht, const gchar *dir) +{ + gchar *cwd; + + ENTER; + cwd = g_get_current_dir(); + DBG("%s\n", dir); + if (g_hash_table_lookup(ht, dir)) + { + DBG("already visited\n"); + goto out; + } + g_hash_table_insert(ht, (gpointer) dir, ht); + if (g_chdir(dir)) + { + ERR("can't chdir to %s\n", dir); + goto out; + } + do_app_dir_real(ht, app_dir_name); + +out: + g_chdir(cwd); + g_free(cwd); + RET(); +} + +static int +xconf_cmp_names(gpointer a, gpointer b) +{ + xconf *aa = a, *bb = b; + gchar *s1 = NULL, *s2 = NULL; + int ret; + + ENTER; + XCG(aa, "name", &s1, str); + XCG(bb, "name", &s2, str); + ret = g_strcmp0(s1, s2); + DBG("cmp %s %s - %d\n", s1, s2, ret); + RET(ret); +} + +static gboolean +dir_changed(const gchar *dir, time_t btime) +{ + GDir *d = NULL; + gchar *cwd; + const gchar *name; + gboolean ret = FALSE; + struct stat buf; + + ENTER; + DBG("%s\n", dir); + if (g_stat(dir, &buf)) + return FALSE; + DBG("dir=%s ct=%lu mt=%lu\n", dir, buf.st_ctime, buf.st_mtime); + if ((ret = buf.st_mtime > btime)) + return TRUE; + + cwd = g_get_current_dir(); + if (g_chdir(dir)) + { + DBG("can't chdir to %s\n", dir); + goto out; + } + if (!(d = g_dir_open(".", 0, NULL))) + { + ERR("can't open dir %s\n", dir); + goto out; + } + + while (!ret && (name = g_dir_read_name(d))) + { + if (g_file_test(name, G_FILE_TEST_IS_DIR)) + ret = dir_changed(name, btime); + else if (!g_str_has_suffix(name, ".desktop")) + continue; + else if (g_stat(name, &buf)) + continue; + DBG("name=%s ct=%lu mt=%lu\n", name, buf.st_ctime, buf.st_mtime); + ret = buf.st_mtime > btime; + } +out: + if (d) + g_dir_close(d); + g_chdir(cwd); + g_free(cwd); + RET(ret); +} + +gboolean +systemmenu_changed(time_t btime) +{ + const gchar * const * dirs; + gboolean ret = FALSE; + gchar *cwd = g_get_current_dir(); + + for (dirs = g_get_system_data_dirs(); *dirs && !ret; dirs++) + { + g_chdir(*dirs); + ret = dir_changed(app_dir_name, btime); + } + + DBG("btime=%lu\n", btime); + if (!ret) + { + g_chdir(g_get_user_data_dir()); + ret = dir_changed(app_dir_name, btime); + } + g_chdir(cwd); + g_free(cwd); + return ret; +} + +xconf * +xconf_new_from_systemmenu() +{ + xconf *xc, *mxc, *tmp; + GSList *w; + GHashTable *ht; + int i; + const gchar * const * dirs; + + /* Create category menus */ + ht = g_hash_table_new(g_str_hash, g_str_equal); + xc = xconf_new("systemmenu", NULL); + for (i = 0; i < G_N_ELEMENTS(main_cats); i++) + { + mxc = xconf_new("menu", NULL); + xconf_append(xc, mxc); + + tmp = xconf_new("name", _(main_cats[i].local_name)); + xconf_append(mxc, tmp); + + tmp = xconf_new("icon", main_cats[i].icon); + xconf_append(mxc, tmp); + + g_hash_table_insert(ht, main_cats[i].name, mxc); + } + + /* Read applications and add them to categories */ + + for (dirs = g_get_system_data_dirs(); *dirs; dirs++) + do_app_dir(ht, *dirs); + do_app_dir(ht, g_get_user_data_dir()); + + /* Delete empty categories */ +retry: + for (w = xc->sons; w; w = g_slist_next(w)) + { + tmp = w->data; + if (!xconf_find(tmp, "item", 0)) + { + xconf_del(tmp, FALSE); + goto retry; + } + } + + /* Sort */ + xc->sons = g_slist_sort(xc->sons, (GCompareFunc) xconf_cmp_names); + for (w = xc->sons; w; w = g_slist_next(w)) + { + tmp = w->data; + tmp->sons = g_slist_sort(tmp->sons, (GCompareFunc) xconf_cmp_names); + } + + g_hash_table_destroy(ht); + + return xc; +} diff --git a/plugins/meter/Makefile b/plugins/meter/Makefile new file mode 100644 index 0000000..6875585 --- /dev/null +++ b/plugins/meter/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +meter_src = meter.c +meter_cflags = -DPLUGIN $(GTK2_CFLAGS) +meter_libs = $(GTK2_LIBS) +meter_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/meter/meter.c b/plugins/meter/meter.c new file mode 100644 index 0000000..0a90278 --- /dev/null +++ b/plugins/meter/meter.c @@ -0,0 +1,113 @@ + +#include "plugin.h" +#include "panel.h" +#include "meter.h" + + +//#define DEBUGPRN +#include "dbg.h" +float roundf(float x); + +/* level - per cent level from 0 to 100 */ +static void +meter_set_level(meter_priv *m, int level) +{ + int i; + GdkPixbuf *pb; + + ENTER; + if (m->level == level) + RET(); + if (!m->num) + RET(); + if (level < 0 || level > 100) { + ERR("meter: illegal level %d\n", level); + RET(); + } + i = roundf((gfloat) level / 100 * (m->num - 1)); + DBG("level=%f icon=%d\n", level, i); + if (i != m->cur_icon) { + m->cur_icon = i; + pb = gtk_icon_theme_load_icon(icon_theme, m->icons[i], + m->size, GTK_ICON_LOOKUP_FORCE_SIZE, NULL); + DBG("loading icon '%s' %s\n", m->icons[i], pb ? "ok" : "failed"); + gtk_image_set_from_pixbuf(GTK_IMAGE(m->meter), pb); + if (pb) + g_object_unref(G_OBJECT(pb)); + } + m->level = level; + RET(); +} + +static void +meter_set_icons(meter_priv *m, gchar **icons) +{ + gchar **s; + + ENTER; + if (m->icons == icons) + RET(); + for (s = icons; *s; s++) + DBG("icon %s\n", *s); + m->num = (s - icons); + DBG("total %d icons\n", m->num); + m->icons = icons; + m->cur_icon = -1; + m->level = -1; + RET(); +} +static void +update_view(meter_priv *m) +{ + ENTER; + m->cur_icon = -1; + meter_set_level(m, m->level); + RET(); +} + +static int +meter_constructor(plugin_instance *p) +{ + meter_priv *m; + + ENTER; + m = (meter_priv *) p; + m->meter = gtk_image_new(); + gtk_misc_set_alignment(GTK_MISC(m->meter), 0.5, 0.5); + gtk_misc_set_padding(GTK_MISC(m->meter), 0, 0); + gtk_widget_show(m->meter); + gtk_container_add(GTK_CONTAINER(p->pwid), m->meter); + m->cur_icon = -1; + m->size = p->panel->max_elem_height; + m->itc_id = g_signal_connect_swapped(G_OBJECT(icon_theme), + "changed", (GCallback) update_view, m); + RET(1); +} + +static void +meter_destructor(plugin_instance *p) +{ + meter_priv *m = (meter_priv *) p; + + ENTER; + g_signal_handler_disconnect(G_OBJECT(icon_theme), m->itc_id); + RET(); +} + +static meter_class class = { + .plugin = { + .type = "meter", + .name = "Meter", + .description = "Basic meter plugin", + .version = "1.0", + .priv_size = sizeof(meter_priv), + + .constructor = meter_constructor, + .destructor = meter_destructor, + }, + .set_level = meter_set_level, + .set_icons = meter_set_icons, +}; + + +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/meter/meter.h b/plugins/meter/meter.h new file mode 100644 index 0000000..89ec6bd --- /dev/null +++ b/plugins/meter/meter.h @@ -0,0 +1,25 @@ +#ifndef meter_H +#define meter_H + +#include "plugin.h" +#include "panel.h" + +typedef struct { + plugin_instance plugin; + gchar **icons; + gint num; + GtkWidget *meter; + gfloat level; + gint cur_icon; + gint size; + gint itc_id; +} meter_priv; + +typedef struct { + plugin_class plugin; + void (*set_level)(meter_priv *c, int val); + void (*set_icons)(meter_priv *c, gchar **icons); +} meter_class; + + +#endif diff --git a/plugins/net/Makefile b/plugins/net/Makefile new file mode 100644 index 0000000..d400a84 --- /dev/null +++ b/plugins/net/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +net_src = net.c +net_cflags = -DPLUGIN $(GTK2_CFLAGS) +net_libs = $(GTK2_LIBS) +net_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/net/net.c b/plugins/net/net.c new file mode 100644 index 0000000..92e2d30 --- /dev/null +++ b/plugins/net/net.c @@ -0,0 +1,233 @@ + +/* + * A little bug fixed by Mykola :) + * FreeBSD support is added by Eygene Ryabinkin + */ + +#include "../chart/chart.h" +#include +#include + +//#define DEBUGPRN +#include "dbg.h" + +#if defined(__FreeBSD__) +#include +#include +#include +#include +#include +#include +#endif + + +#define CHECK_PERIOD 2 /* second */ + +struct net_stat { + gulong tx, rx; +}; + +typedef struct { + chart_priv chart; + struct net_stat net_prev; + int timer; + char *iface; +#if defined(__FreeBSD__) + size_t ifmib_row; +#endif + gint max_tx; + gint max_rx; + gulong max; + gchar *colors[2]; +} net_priv; + +static chart_class *k; + + +static void net_destructor(plugin_instance *p); + + +#if defined __linux__ + +#define init_net_stats(x) + +static int +net_get_load_real(net_priv *c, struct net_stat *net) +{ + FILE *stat; + char buf[256], *s = NULL; + + stat = fopen("/proc/net/dev", "r"); + if(!stat) + return -1; + fgets(buf, 256, stat); + fgets(buf, 256, stat); + + while (!s && !feof(stat) && fgets(buf, 256, stat)) + s = g_strrstr(buf, c->iface); + fclose(stat); + if (!s) + return -1; + s = g_strrstr(s, ":"); + if (!s) + return -1; + s++; + if (sscanf(s, + "%lu %*d %*d %*d %*d %*d %*d %*d %lu", + &net->rx, &net->tx)!= 2) { + DBG("can't read %s statistics\n", c->iface); + return -1; + } + return 0; +} + +#elif defined(__FreeBSD__) +static void +init_net_stats(net_priv *c) +{ + int mib[6] = { + CTL_NET, + PF_LINK, + NETLINK_GENERIC, + IFMIB_SYSTEM, + IFMIB_IFCOUNT + }; + u_int count = 0; + struct ifmibdata ifmd; + size_t len = sizeof(count); + + c->ifmib_row = 0; + if (sysctl(mib, 5, (void *)&count, &len, NULL, 0) != 0) + return; + + mib[3] = IFMIB_IFDATA; + mib[5] = IFDATA_GENERAL; + len = sizeof(ifmd); + for (mib[4] = 1; mib[4] <= count; mib[4]++) { + if (sysctl(mib, 6, (void *)&ifmd, &len, NULL, 0) != 0) + continue; + if (strcmp(ifmd.ifmd_name, c->iface) == 0) { + c->ifmib_row = mib[4]; + break; + } + } +} + +static int +net_get_load_real(net_priv *c, struct net_stat *net) +{ + int mib[6] = { + CTL_NET, + PF_LINK, + NETLINK_GENERIC, + IFMIB_IFDATA, + c->ifmib_row, + IFDATA_GENERAL + }; + struct ifmibdata ifmd; + size_t len = sizeof(ifmd); + + if (!c->ifmib_row) + return -1; + + if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), &ifmd, &len, NULL, 0) != 0) + return -1; + + net->tx = ifmd.ifmd_data.ifi_obytes; + net->rx = ifmd.ifmd_data.ifi_ibytes; + return 0; +} + +#endif + +static int +net_get_load(net_priv *c) +{ + struct net_stat net, net_diff; + float total[2]; + char buf[256]; + + ENTER; + memset(&net, 0, sizeof(net)); + memset(&net_diff, 0, sizeof(net_diff)); + memset(&total, 0, sizeof(total)); + + if (net_get_load_real(c, &net)) + goto end; + + net_diff.tx = ((net.tx - c->net_prev.tx) >> 10) / CHECK_PERIOD; + net_diff.rx = ((net.rx - c->net_prev.rx) >> 10) / CHECK_PERIOD; + + c->net_prev = net; + total[0] = (float)(net_diff.tx) / c->max; + total[1] = (float)(net_diff.rx) / c->max; + +end: + DBG("%f %f %ul %ul\n", total[0], total[1], net_diff.tx, net_diff.rx); + k->add_tick(&c->chart, total); + g_snprintf(buf, sizeof(buf), "%s:\nD %lu Kbs, U %lu Kbs", + c->iface, net_diff.rx, net_diff.tx); + gtk_widget_set_tooltip_markup(((plugin_instance *)c)->pwid, buf); + RET(TRUE); +} + +static int +net_constructor(plugin_instance *p) +{ + net_priv *c; + + if (!(k = class_get("chart"))) + RET(0); + if (!PLUGIN_CLASS(k)->constructor(p)) + RET(0); + c = (net_priv *) p; + + c->iface = "eth0"; + c->max_rx = 120; + c->max_tx = 12; + c->colors[0] = "violet"; + c->colors[1] = "blue"; + XCG(p->xc, "interface", &c->iface, str); + XCG(p->xc, "RxLimit", &c->max_rx, int); + XCG(p->xc, "TxLimit", &c->max_tx, int); + XCG(p->xc, "TxColor", &c->colors[0], str); + XCG(p->xc, "RxColor", &c->colors[1], str); + + init_net_stats(c); + + c->max = c->max_rx + c->max_tx; + k->set_rows(&c->chart, 2, c->colors); + gtk_widget_set_tooltip_markup(((plugin_instance *)c)->pwid, "Net"); + net_get_load(c); + c->timer = g_timeout_add(CHECK_PERIOD * 1000, + (GSourceFunc) net_get_load, (gpointer) c); + RET(1); +} + + +static void +net_destructor(plugin_instance *p) +{ + net_priv *c = (net_priv *) p; + + ENTER; + if (c->timer) + g_source_remove(c->timer); + PLUGIN_CLASS(k)->destructor(p); + class_put("chart"); + RET(); +} + + +static plugin_class class = { + .count = 0, + .type = "net", + .name = "Net usage", + .version = "1.0", + .description = "Display net usage", + .priv_size = sizeof(net_priv), + + .constructor = net_constructor, + .destructor = net_destructor, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/pager/Makefile b/plugins/pager/Makefile new file mode 100644 index 0000000..0970f35 --- /dev/null +++ b/plugins/pager/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +pager_src = pager.c +pager_cflags = -DPLUGIN $(GTK2_CFLAGS) +pager_libs = $(GTK2_LIBS) +pager_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/pager/pager.c b/plugins/pager/pager.c new file mode 100644 index 0000000..b2bc877 --- /dev/null +++ b/plugins/pager/pager.c @@ -0,0 +1,850 @@ +/* pager.c -- pager module of fbpanel project + * + * Copyright (C) 2002-2003 Anatoly Asviyan + * Joe MacDonald + * + * This file is part of fbpanel. + * + * fbpanel is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * fbpanel is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sawfish; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include +#include +#include +#include +#include + + +#include + +#include "panel.h" +#include "misc.h" +#include "plugin.h" +#include "gtkbgbox.h" + +//#define DEBUGPRN +#include "dbg.h" + + + +/* managed window: all related info that wm holds about its managed windows */ +typedef struct _task { + Window win; + int x, y; + guint w, h; + gint refcount; + guint stacking; + guint desktop; + char *name, *iname; + net_wm_state nws; + net_wm_window_type nwwt; +} task; + +typedef struct _desk desk; +typedef struct _pager_priv pager_priv; + +#define MAX_DESK_NUM 20 +/* map of a desktop */ +struct _desk { + GtkWidget *da; + Pixmap xpix; + GdkPixmap *gpix; + GdkPixmap *pix; + guint no, dirty, first; + gfloat scalew, scaleh; + pager_priv *pg; +}; + +struct _pager_priv { + plugin_instance plugin; + GtkWidget *box; + desk *desks[MAX_DESK_NUM]; + guint desknum; + guint curdesk; + gint wallpaper; + //int dw, dh; + gfloat /*scalex, scaley, */ratio; + Window *wins; + int winnum, dirty; + GHashTable* htable; + task *focusedtask; + FbBg *fbbg; + gint dah, daw; +}; + + + +#define TASK_VISIBLE(tk) \ + (!( (tk)->nws.hidden || (tk)->nws.skip_pager )) + + +static void pager_rebuild_all(FbEv *ev, pager_priv *pg); +static void desk_draw_bg(pager_priv *pg, desk *d1); +//static void pager_paint_frame(pager_priv *pg, gint no, GtkStateType state); + +static void pager_destructor(plugin_instance *p); + +static inline void desk_set_dirty_by_win(pager_priv *p, task *t); +static inline void desk_set_dirty(desk *d); +static inline void desk_set_dirty_all(pager_priv *pg); + +/* +static void desk_clear_pixmap(desk *d); +static gboolean task_remove_stale(Window *win, task *t, pager_priv *p); +static gboolean task_remove_all(Window *win, task *t, pager_priv *p); +*/ + +#ifdef EXTRA_DEBUG +static pager_priv *cp; + +/* debug func to print ids of all managed windows on USR2 signal */ +static void +sig_usr(int signum) +{ + int j; + task *t; + + if (signum != SIGUSR2) + return; + ERR("dekstop num=%d cur_desktop=%d\n", cp->desknum, cp->curdesk); + for (j = 0; j < cp->winnum; j++) { + if (!(t = g_hash_table_lookup(cp->htable, &cp->wins[j]))) + continue; + ERR("win=%x desktop=%u\n", (guint) t->win, t->desktop); + } + +} +#endif + + +/***************************************************************** + * Task Management Routines * + *****************************************************************/ + + +/* tell to remove element with zero refcount */ +static gboolean +task_remove_stale(Window *win, task *t, pager_priv *p) +{ + if (t->refcount-- == 0) { + desk_set_dirty_by_win(p, t); + if (p->focusedtask == t) + p->focusedtask = NULL; + DBG("del %lx\n", t->win); + g_free(t); + return TRUE; + } + return FALSE; +} + +/* tell to remove element with zero refcount */ +static gboolean +task_remove_all(Window *win, task *t, pager_priv *p) +{ + g_free(t); + return TRUE; +} + + +static void +task_get_sizepos(task *t) +{ + Window root, junkwin; + int rx, ry; + guint dummy; + XWindowAttributes win_attributes; + + ENTER; + if (!XGetWindowAttributes(GDK_DISPLAY(), t->win, &win_attributes)) { + if (!XGetGeometry (GDK_DISPLAY(), t->win, &root, &t->x, &t->y, &t->w, &t->h, + &dummy, &dummy)) { + t->x = t->y = t->w = t->h = 2; + } + + } else { + XTranslateCoordinates (GDK_DISPLAY(), t->win, win_attributes.root, + -win_attributes.border_width, + -win_attributes.border_width, + &rx, &ry, &junkwin); + t->x = rx; + t->y = ry; + t->w = win_attributes.width; + t->h = win_attributes.height; + DBG("win=0x%lx WxH=%dx%d\n", t->win,t->w, t->h); + } + RET(); +} + + +static void +task_update_pix(task *t, desk *d) +{ + int x, y, w, h; + GtkWidget *widget; + + ENTER; + g_return_if_fail(d->pix != NULL); + if (!TASK_VISIBLE(t)) + RET(); + + if (t->desktop < d->pg->desknum && + t->desktop != d->no) + RET(); + + x = (gfloat)t->x * d->scalew; + y = (gfloat)t->y * d->scaleh; + w = (gfloat)t->w * d->scalew; + //h = (gfloat)t->h * d->scaleh; + h = (t->nws.shaded) ? 3 : (gfloat)t->h * d->scaleh; + if (w < 3 || h < 3) + RET(); + widget = GTK_WIDGET(d->da); + gdk_draw_rectangle (d->pix, + (d->pg->focusedtask == t) ? + widget->style->bg_gc[GTK_STATE_SELECTED] : + widget->style->bg_gc[GTK_STATE_NORMAL], + TRUE, + x+1, y+1, w-1, h-1); + gdk_draw_rectangle (d->pix, + (d->pg->focusedtask == t) ? + widget->style->fg_gc[GTK_STATE_SELECTED] : + widget->style->fg_gc[GTK_STATE_NORMAL], + FALSE, + x, y, w, h); + RET(); +} + + +/***************************************************************** + * Desk Functions * + *****************************************************************/ +static void +desk_clear_pixmap(desk *d) +{ + GtkWidget *widget; + + ENTER; + DBG("d->no=%d\n", d->no); + if (!d->pix) + RET(); + widget = GTK_WIDGET(d->da); + if (d->pg->wallpaper && d->xpix != None) { + gdk_draw_drawable (d->pix, + widget->style->dark_gc[GTK_STATE_NORMAL], + d->gpix, + 0, 0, 0, 0, + widget->allocation.width, + widget->allocation.height); + } else { + gdk_draw_rectangle (d->pix, + ((d->no == d->pg->curdesk) ? + widget->style->dark_gc[GTK_STATE_SELECTED] : + widget->style->dark_gc[GTK_STATE_NORMAL]), + TRUE, + 0, 0, + widget->allocation.width, + widget->allocation.height); + } + if (d->pg->wallpaper && d->no == d->pg->curdesk) + gdk_draw_rectangle (d->pix, + widget->style->light_gc[GTK_STATE_SELECTED], + FALSE, + 0, 0, + widget->allocation.width -1, + widget->allocation.height -1); + RET(); +} + + +static void +desk_draw_bg(pager_priv *pg, desk *d1) +{ + Pixmap xpix; + GdkPixmap *gpix; + GdkPixbuf *p1, *p2; + gint width, height, depth; + FbBg *bg = pg->fbbg; + GtkWidget *widget = d1->da; + + ENTER; + if (d1->no) { + desk *d0 = d1->pg->desks[0]; + if (d0->gpix && d0->xpix != None + && d0->da->allocation.width == widget->allocation.width + && d0->da->allocation.height == widget->allocation.height) { + gdk_draw_drawable(d1->gpix, + widget->style->fg_gc[GTK_WIDGET_STATE (widget)], + d0->gpix,0, 0, 0, 0, + widget->allocation.width, + widget->allocation.height); + d1->xpix = d0->xpix; + DBG("copy gpix from d0 to d%d\n", d1->no); + RET(); + } + } + xpix = fb_bg_get_xrootpmap(bg); + d1->xpix = None; + width = widget->allocation.width; + height = widget->allocation.height; + DBG("w %d h %d\n", width, height); + if (width < 3 || height < 3) + RET(); + + // create new pix + xpix = fb_bg_get_xrootpmap(bg); + if (xpix == None) + RET(); + depth = gdk_drawable_get_depth(widget->window); + gpix = fb_bg_get_xroot_pix_for_area(bg, 0, 0, gdk_screen_width(), gdk_screen_height(), depth); + if (!gpix) { + ERR("fb_bg_get_xroot_pix_for_area failed\n"); + RET(); + } + p1 = gdk_pixbuf_get_from_drawable(NULL, gpix, NULL, 0, 0, 0, 0, + gdk_screen_width(), gdk_screen_height()); + if (!p1) { + ERR("gdk_pixbuf_get_from_drawable failed\n"); + goto err_gpix; + } + p2 = gdk_pixbuf_scale_simple(p1, width, height, + //GDK_INTERP_NEAREST + //GDK_INTERP_TILES + //GDK_INTERP_BILINEAR + GDK_INTERP_HYPER + ); + if (!p2) { + ERR("gdk_pixbuf_scale_simple failed\n"); + goto err_p1; + } + gdk_draw_pixbuf(d1->gpix, widget->style->fg_gc[GTK_WIDGET_STATE (widget)], + p2, 0, 0, 0, 0, width, height, GDK_RGB_DITHER_NONE, 0, 0); + + d1->xpix = xpix; + g_object_unref(p2); + err_p1: + g_object_unref(p1); + err_gpix: + g_object_unref(gpix); + RET(); +} + + + +static inline void +desk_set_dirty(desk *d) +{ + ENTER; + d->dirty = 1; + gtk_widget_queue_draw(d->da); + RET(); +} + +static inline void +desk_set_dirty_all(pager_priv *pg) +{ + int i; + ENTER; + for (i = 0; i < pg->desknum; i++) + desk_set_dirty(pg->desks[i]); + RET(); +} + +static inline void +desk_set_dirty_by_win(pager_priv *p, task *t) +{ + ENTER; + if (t->nws.skip_pager || t->nwwt.desktop /*|| t->nwwt.dock || t->nwwt.splash*/ ) + RET(); + if (t->desktop < p->desknum) + desk_set_dirty(p->desks[t->desktop]); + else + desk_set_dirty_all(p); + RET(); +} + +/* Redraw the screen from the backing pixmap */ +static gint +desk_expose_event (GtkWidget *widget, GdkEventExpose *event, desk *d) +{ + ENTER; + DBG("d->no=%d\n", d->no); + + if (d->dirty) { + pager_priv *pg = d->pg; + task *t; + int j; + + d->dirty = 0; + desk_clear_pixmap(d); + for (j = 0; j < pg->winnum; j++) { + if (!(t = g_hash_table_lookup(pg->htable, &pg->wins[j]))) + continue; + task_update_pix(t, d); + } + } + gdk_draw_drawable(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE (widget)], + d->pix, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + RET(FALSE); +} + + +/* Upon realize and every resize creates a new backing pixmap of the appropriate size */ +static gint +desk_configure_event (GtkWidget *widget, GdkEventConfigure *event, desk *d) +{ + int w, h; + + ENTER; + w = widget->allocation.width; + h = widget->allocation.height; + + DBG("d->no=%d %dx%d %dx%d\n", d->no, w, h, d->pg->daw, d->pg->dah); + if (d->pix) + g_object_unref(d->pix); + if (d->gpix) + g_object_unref(d->gpix); + d->pix = gdk_pixmap_new(widget->window, w, h, -1); + if (d->pg->wallpaper) { + d->gpix = gdk_pixmap_new(widget->window, w, h, -1); + desk_draw_bg(d->pg, d); + } + d->scalew = (gfloat)h / (gfloat)gdk_screen_height(); + d->scaleh = (gfloat)w / (gfloat)gdk_screen_width(); + desk_set_dirty(d); + RET(FALSE); +} + +static gint +desk_button_press_event(GtkWidget * widget, GdkEventButton * event, desk *d) +{ + ENTER; + if (event->type == GDK_BUTTON_PRESS && event->button == 3 + && event->state & GDK_CONTROL_MASK) { + RET(FALSE); + } + DBG("s=%d\n", d->no); + Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, d->no, 0, 0, 0, 0); + RET(TRUE); +} + +static void +desk_new(pager_priv *pg, int i) +{ + desk *d; + + ENTER; + g_assert(i < pg->desknum); + d = pg->desks[i] = g_new0(desk, 1); + d->pg = pg; + d->pix = NULL; + d->dirty = 0; + d->first = 1; + d->no = i; + + d->da = gtk_drawing_area_new(); + gtk_widget_set_size_request(d->da, pg->daw, pg->dah); + gtk_box_pack_start(GTK_BOX(pg->box), d->da, TRUE, TRUE, 0); + gtk_widget_add_events (d->da, GDK_EXPOSURE_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK); + g_signal_connect (G_OBJECT (d->da), "expose_event", + (GCallback) desk_expose_event, (gpointer)d); + g_signal_connect (G_OBJECT (d->da), "configure_event", + (GCallback) desk_configure_event, (gpointer)d); + g_signal_connect (G_OBJECT (d->da), "button_press_event", + (GCallback) desk_button_press_event, (gpointer)d); + gtk_widget_show_all(d->da); + RET(); +} + +static void +desk_free(pager_priv *pg, int i) +{ + desk *d; + + ENTER; + d = pg->desks[i]; + DBG("i=%d d->no=%d d->da=%p d->pix=%p\n", + i, d->no, d->da, d->pix); + if (d->pix) + g_object_unref(d->pix); + if (d->gpix) + g_object_unref(d->gpix); + gtk_widget_destroy(d->da); + g_free(d); + RET(); +} + + +/***************************************************************** + * Netwm/WM Interclient Communication * + *****************************************************************/ + +static void +do_net_active_window(FbEv *ev, pager_priv *p) +{ + Window *fwin; + task *t; + + ENTER; + fwin = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_ACTIVE_WINDOW, XA_WINDOW, 0); + DBG("win=%lx\n", fwin ? *fwin : 0); + if (fwin) { + t = g_hash_table_lookup(p->htable, fwin); + if (t != p->focusedtask) { + if (p->focusedtask) + desk_set_dirty_by_win(p, p->focusedtask); + p->focusedtask = t; + if (t) + desk_set_dirty_by_win(p, t); + } + XFree(fwin); + } else { + if (p->focusedtask) { + desk_set_dirty_by_win(p, p->focusedtask); + p->focusedtask = NULL; + } + } + RET(); +} + +static void +do_net_current_desktop(FbEv *ev, pager_priv *pg) +{ + ENTER; + desk_set_dirty(pg->desks[pg->curdesk]); + gtk_widget_set_state(pg->desks[pg->curdesk]->da, GTK_STATE_NORMAL); + //pager_paint_frame(pg, pg->curdesk, GTK_STATE_NORMAL); + pg->curdesk = get_net_current_desktop (); + if (pg->curdesk >= pg->desknum) + pg->curdesk = 0; + desk_set_dirty(pg->desks[pg->curdesk]); + gtk_widget_set_state(pg->desks[pg->curdesk]->da, GTK_STATE_SELECTED); + //pager_paint_frame(pg, pg->curdesk, GTK_STATE_SELECTED); + RET(); +} + + +static void +do_net_client_list_stacking(FbEv *ev, pager_priv *p) +{ + int i; + task *t; + + ENTER; + if (p->wins) + XFree(p->wins); + p->wins = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_CLIENT_LIST_STACKING, + XA_WINDOW, &p->winnum); + if (!p->wins || !p->winnum) + RET(); + + /* refresh existing tasks and add new */ + for (i = 0; i < p->winnum; i++) { + if ((t = g_hash_table_lookup(p->htable, &p->wins[i]))) { + t->refcount++; + if (t->stacking != i) { + t->stacking = i; + desk_set_dirty_by_win(p, t); + } + } else { + t = g_new0(task, 1); + t->refcount++; + t->win = p->wins[i]; + if (!FBPANEL_WIN(t->win)) + XSelectInput (GDK_DISPLAY(), t->win, PropertyChangeMask | StructureNotifyMask); + t->desktop = get_net_wm_desktop(t->win); + get_net_wm_state(t->win, &t->nws); + get_net_wm_window_type(t->win, &t->nwwt); + task_get_sizepos(t); + g_hash_table_insert(p->htable, &t->win, t); + DBG("add %lx\n", t->win); + desk_set_dirty_by_win(p, t); + } + } + /* pass throu hash table and delete stale windows */ + g_hash_table_foreach_remove(p->htable, (GHRFunc) task_remove_stale, (gpointer)p); + RET(); +} + + +/***************************************************************** + * Pager Functions * + *****************************************************************/ +/* +static void +pager_unmapnotify(pager_priv *p, XEvent *ev) +{ + Window win = ev->xunmap.window; + task *t; + if (!(t = g_hash_table_lookup(p->htable, &win))) + RET(); + DBG("pager_unmapnotify: win=0x%x\n", win); + RET(); + t->ws = WithdrawnState; + desk_set_dirty_by_win(p, t); + RET(); +} +*/ +static void +pager_configurenotify(pager_priv *p, XEvent *ev) +{ + Window win = ev->xconfigure.window; + task *t; + + ENTER; + + if (!(t = g_hash_table_lookup(p->htable, &win))) + RET(); + DBG("win=0x%lx\n", win); + task_get_sizepos(t); + desk_set_dirty_by_win(p, t); + RET(); +} + +static void +pager_propertynotify(pager_priv *p, XEvent *ev) +{ + Atom at = ev->xproperty.atom; + Window win = ev->xproperty.window; + task *t; + + + ENTER; + if ((win == GDK_ROOT_WINDOW()) || !(t = g_hash_table_lookup(p->htable, &win))) + RET(); + + DBG("window=0x%lx\n", t->win); + if (at == a_NET_WM_STATE) { + DBG("event=NET_WM_STATE\n"); + get_net_wm_state(t->win, &t->nws); + } else if (at == a_NET_WM_DESKTOP) { + DBG("event=NET_WM_DESKTOP\n"); + desk_set_dirty_by_win(p, t); // to clean up desks where this task was + t->desktop = get_net_wm_desktop(t->win); + } else { + RET(); + } + desk_set_dirty_by_win(p, t); + RET(); +} + +static GdkFilterReturn +pager_event_filter( XEvent *xev, GdkEvent *event, pager_priv *pg) +{ + ENTER; + if (xev->type == PropertyNotify ) + pager_propertynotify(pg, xev); + else if (xev->type == ConfigureNotify ) + pager_configurenotify(pg, xev); + RET(GDK_FILTER_CONTINUE); +} +#if 0 +static void +pager_paint_frame(pager_priv *pg, gint no, GtkStateType state) +{ + gint x, y, w, h, border; + + ENTER; + RET(); + //desk_set_dirty(pg->desks[no]); + border = gtk_container_get_border_width(GTK_CONTAINER(pg->box)); + w = pg->box->allocation.width; + h = pg->desks[0]->da->allocation.height + border; + x = 0; + y = h * no; + h += border; + DBG("%d: %d %d %d %d\n", no, x, y, w, h); + gtk_paint_flat_box(pg->box->style, pg->box->window, + state, + GTK_SHADOW_NONE, + NULL, pg->box, "box", + x + 1, y + 1,w, h); + RET(); + +} + +static gint +pager_expose_event (GtkWidget *widget, GdkEventExpose *event, pager_priv *pg) +{ + ENTER; + DBG("curdesk=%d\n", pg->curdesk); + pager_paint_frame(pg, pg->curdesk, GTK_STATE_SELECTED); + RET(TRUE); +} +#endif + +static void +pager_bg_changed(FbBg *bg, pager_priv *pg) +{ + int i; + + ENTER; + for (i = 0; i < pg->desknum; i++) { + desk *d = pg->desks[i]; + desk_draw_bg(pg, d); + desk_set_dirty(d); + } + RET(); +} + + +static void +pager_rebuild_all(FbEv *ev, pager_priv *pg) +{ + int desknum, curdesk, dif, i; + + ENTER; + desknum = pg->desknum; + curdesk = pg->curdesk; + + pg->desknum = get_net_number_of_desktops(); + if (pg->desknum < 1) + pg->desknum = 1; + else if (pg->desknum > MAX_DESK_NUM) { + pg->desknum = MAX_DESK_NUM; + ERR("pager: max number of supported desks is %d\n", MAX_DESK_NUM); + } + pg->curdesk = get_net_current_desktop(); + if (pg->curdesk >= pg->desknum) + pg->curdesk = 0; + DBG("desknum=%d curdesk=%d\n", desknum, curdesk); + DBG("pg->desknum=%d pg->curdesk=%d\n", pg->desknum, pg->curdesk); + dif = pg->desknum - desknum; + + if (dif == 0) + RET(); + + if (dif < 0) { + /* if desktops were deleted then delete their maps also */ + for (i = pg->desknum; i < desknum; i++) + desk_free(pg, i); + } else { + for (i = desknum; i < pg->desknum; i++) + desk_new(pg, i); + } + g_hash_table_foreach_remove(pg->htable, (GHRFunc) task_remove_all, (gpointer)pg); + do_net_current_desktop(NULL, pg); + do_net_client_list_stacking(NULL, pg); + + RET(); +} + +#define BORDER 1 +static int +pager_constructor(plugin_instance *plug) +{ + pager_priv *pg; + + ENTER; + pg = (pager_priv *) plug; + +#ifdef EXTRA_DEBUG + cp = pg; + signal(SIGUSR2, sig_usr); +#endif + + pg->htable = g_hash_table_new (g_int_hash, g_int_equal); + pg->box = plug->panel->my_box_new(TRUE, 1); + gtk_container_set_border_width (GTK_CONTAINER (pg->box), 0); + gtk_widget_show(pg->box); + + gtk_bgbox_set_background(plug->pwid, BG_STYLE, 0, 0); + gtk_container_set_border_width (GTK_CONTAINER (plug->pwid), BORDER); + gtk_container_add(GTK_CONTAINER(plug->pwid), pg->box); + + pg->ratio = (gfloat)gdk_screen_width() / (gfloat)gdk_screen_height(); + if (plug->panel->orientation == GTK_ORIENTATION_HORIZONTAL) { + pg->dah = plug->panel->ah - 2 * BORDER; + pg->daw = (gfloat) pg->dah * pg->ratio; + } else { + pg->daw = plug->panel->aw - 2 * BORDER; + pg->dah = (gfloat) pg->daw / pg->ratio; + } + pg->wallpaper = 1; + //pg->scaley = (gfloat)pg->dh / (gfloat)gdk_screen_height(); + //pg->scalex = (gfloat)pg->dw / (gfloat)gdk_screen_width(); + XCG(plug->xc, "showwallpaper", &pg->wallpaper, enum, bool_enum); + if (pg->wallpaper) { + pg->fbbg = fb_bg_get_for_display(); + DBG("get fbbg %p\n", pg->fbbg); + g_signal_connect(G_OBJECT(pg->fbbg), "changed", + G_CALLBACK(pager_bg_changed), pg); + } + pager_rebuild_all(fbev, pg); + + gdk_window_add_filter(NULL, (GdkFilterFunc)pager_event_filter, pg ); + + g_signal_connect (G_OBJECT (fbev), "current_desktop", + G_CALLBACK (do_net_current_desktop), (gpointer) pg); + g_signal_connect (G_OBJECT (fbev), "active_window", + G_CALLBACK (do_net_active_window), (gpointer) pg); + g_signal_connect (G_OBJECT (fbev), "number_of_desktops", + G_CALLBACK (pager_rebuild_all), (gpointer) pg); + g_signal_connect (G_OBJECT (fbev), "client_list_stacking", + G_CALLBACK (do_net_client_list_stacking), (gpointer) pg); + RET(1); +} + +static void +pager_destructor(plugin_instance *p) +{ + pager_priv *pg = (pager_priv *)p; + + ENTER; + g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), + do_net_current_desktop, pg); + g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), + do_net_active_window, pg); + g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), + pager_rebuild_all, pg); + g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), + do_net_client_list_stacking, pg); + gdk_window_remove_filter(NULL, (GdkFilterFunc)pager_event_filter, pg); + while (pg->desknum--) { + desk_free(pg, pg->desknum); + } + g_hash_table_foreach_remove(pg->htable, (GHRFunc) task_remove_all, + (gpointer)pg); + g_hash_table_destroy(pg->htable); + gtk_widget_destroy(pg->box); + if (pg->wallpaper) { + g_signal_handlers_disconnect_by_func(G_OBJECT (pg->fbbg), + pager_bg_changed, pg); + DBG("put fbbg %p\n", pg->fbbg); + g_object_unref(pg->fbbg); + } + if (pg->wins) + XFree(pg->wins); + RET(); +} + + +static plugin_class class = { + .fname = NULL, + .count = 0, + .type = "pager", + .name = "Pager", + .version = "1.0", + .description = "Pager shows thumbnails of your desktops", + .priv_size = sizeof(pager_priv), + + .constructor = pager_constructor, + .destructor = pager_destructor, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/separator/Makefile b/plugins/separator/Makefile new file mode 100644 index 0000000..b748834 --- /dev/null +++ b/plugins/separator/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +separator_src = separator.c +separator_cflags = -DPLUGIN $(GTK2_CFLAGS) +separator_libs = $(GTK2_LIBS) +separator_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/separator/separator.c b/plugins/separator/separator.c new file mode 100644 index 0000000..ca5fe37 --- /dev/null +++ b/plugins/separator/separator.c @@ -0,0 +1,43 @@ + + +#include "panel.h" +#include "misc.h" +#include "plugin.h" + + +//#define DEBUGPRN +#include "dbg.h" + + +static int +separator_constructor(plugin_instance *p) +{ + GtkWidget *sep; + + ENTER; + sep = p->panel->my_separator_new(); + gtk_container_add(GTK_CONTAINER(p->pwid), sep); + gtk_widget_show_all(p->pwid); + RET(1); +} + +static void +separator_destructor(plugin_instance *p) +{ + ENTER; + RET(); +} + + +static plugin_class class = { + .count = 0, + .type = "separator", + .name = "Separator", + .version = "1.0", + .description = "Separator line", + .priv_size = sizeof(plugin_instance), + + .constructor = separator_constructor, + .destructor = separator_destructor, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/space/Makefile b/plugins/space/Makefile new file mode 100644 index 0000000..d735c28 --- /dev/null +++ b/plugins/space/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +space_src = space.c +space_cflags = -DPLUGIN $(GTK2_CFLAGS) +space_libs = $(GTK2_LIBS) +space_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/space/space.c b/plugins/space/space.c new file mode 100644 index 0000000..b814227 --- /dev/null +++ b/plugins/space/space.c @@ -0,0 +1,59 @@ +#include + +#include + +#include "panel.h" +#include "misc.h" +#include "plugin.h" + +//#define DEBUGPRN +#include "dbg.h" + + +typedef struct { + plugin_instance plugin; + int size; + GtkWidget *mainw; + +} space_priv; + +static void +space_destructor(plugin_instance *p) +{ + ENTER; + RET(); +} + +static int +space_constructor(plugin_instance *p) +{ + int w, h, size; + + ENTER; + size = 1; + XCG(p->xc, "size", &size, int); + + if (p->panel->orientation == GTK_ORIENTATION_HORIZONTAL) { + h = 2; + w = size; + } else { + w = 2; + h = size; + } + gtk_widget_set_size_request(p->pwid, w, h); + RET(1); +} + +static plugin_class class = { + .fname = NULL, + .count = 0, + .type = "space", + .name = "Space", + .version = "1.0", + .description = "Ocupy space in a panel", + .priv_size = sizeof(space_priv), + + .constructor = space_constructor, + .destructor = space_destructor, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/systray/Makefile b/plugins/systray/Makefile new file mode 100644 index 0000000..be212ca --- /dev/null +++ b/plugins/systray/Makefile @@ -0,0 +1,13 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +tray_src = egg-marshal.c \ + eggtraymanager.c \ + fixedtip.c \ + main.c +tray_cflags = -DPLUGIN $(GTK2_CFLAGS) +tray_libs = $(GTK2_LIBS) +tray_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/systray/egg-marshal.c b/plugins/systray/egg-marshal.c new file mode 100644 index 0000000..2fb5bb4 --- /dev/null +++ b/plugins/systray/egg-marshal.c @@ -0,0 +1,2 @@ +#include "eggmarshalers.h" +#include "eggmarshalers.c" diff --git a/plugins/systray/eggmarshalers.c b/plugins/systray/eggmarshalers.c new file mode 100644 index 0000000..a9b03e9 --- /dev/null +++ b/plugins/systray/eggmarshalers.c @@ -0,0 +1,164 @@ + +#include + + +#ifdef G_ENABLE_DEBUG +#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) +#define g_marshal_value_peek_char(v) g_value_get_char (v) +#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) +#define g_marshal_value_peek_int(v) g_value_get_int (v) +#define g_marshal_value_peek_uint(v) g_value_get_uint (v) +#define g_marshal_value_peek_long(v) g_value_get_long (v) +#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) +#define g_marshal_value_peek_int64(v) g_value_get_int64 (v) +#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) +#define g_marshal_value_peek_enum(v) g_value_get_enum (v) +#define g_marshal_value_peek_flags(v) g_value_get_flags (v) +#define g_marshal_value_peek_float(v) g_value_get_float (v) +#define g_marshal_value_peek_double(v) g_value_get_double (v) +#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) +#define g_marshal_value_peek_param(v) g_value_get_param (v) +#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) +#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) +#define g_marshal_value_peek_object(v) g_value_get_object (v) +#else /* !G_ENABLE_DEBUG */ +/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. + * Do not access GValues directly in your code. Instead, use the + * g_value_get_*() functions + */ +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int +#define g_marshal_value_peek_char(v) (v)->data[0].v_int +#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint +#define g_marshal_value_peek_long(v) (v)->data[0].v_long +#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 +#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 +#define g_marshal_value_peek_enum(v) (v)->data[0].v_int +#define g_marshal_value_peek_flags(v) (v)->data[0].v_uint +#define g_marshal_value_peek_float(v) (v)->data[0].v_float +#define g_marshal_value_peek_double(v) (v)->data[0].v_double +#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer +#endif /* !G_ENABLE_DEBUG */ + + +/* VOID:OBJECT,OBJECT (eggmarshalers.list:1) */ +void +_egg_marshal_VOID__OBJECT_OBJECT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__OBJECT_OBJECT) (gpointer data1, + gpointer arg_1, + gpointer arg_2, + gpointer data2); + register GMarshalFunc_VOID__OBJECT_OBJECT callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__OBJECT_OBJECT) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_object (param_values + 1), + g_marshal_value_peek_object (param_values + 2), + data2); +} + +/* VOID:OBJECT,STRING,LONG,LONG (eggmarshalers.list:2) */ +void +_egg_marshal_VOID__OBJECT_STRING_LONG_LONG (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__OBJECT_STRING_LONG_LONG) (gpointer data1, + gpointer arg_1, + gpointer arg_2, + glong arg_3, + glong arg_4, + gpointer data2); + register GMarshalFunc_VOID__OBJECT_STRING_LONG_LONG callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + + g_return_if_fail (n_param_values == 5); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__OBJECT_STRING_LONG_LONG) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_object (param_values + 1), + g_marshal_value_peek_string (param_values + 2), + g_marshal_value_peek_long (param_values + 3), + g_marshal_value_peek_long (param_values + 4), + data2); +} + +/* VOID:OBJECT,LONG (eggmarshalers.list:3) */ +void +_egg_marshal_VOID__OBJECT_LONG (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__OBJECT_LONG) (gpointer data1, + gpointer arg_1, + glong arg_2, + gpointer data2); + register GMarshalFunc_VOID__OBJECT_LONG callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__OBJECT_LONG) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_object (param_values + 1), + g_marshal_value_peek_long (param_values + 2), + data2); +} + diff --git a/plugins/systray/eggmarshalers.h b/plugins/systray/eggmarshalers.h new file mode 100644 index 0000000..4ac58bf --- /dev/null +++ b/plugins/systray/eggmarshalers.h @@ -0,0 +1,36 @@ + +#ifndef ___egg_marshal_MARSHAL_H__ +#define ___egg_marshal_MARSHAL_H__ + +#include + +G_BEGIN_DECLS + +/* VOID:OBJECT,OBJECT (eggmarshalers.list:1) */ +extern void _egg_marshal_VOID__OBJECT_OBJECT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* VOID:OBJECT,STRING,LONG,LONG (eggmarshalers.list:2) */ +extern void _egg_marshal_VOID__OBJECT_STRING_LONG_LONG (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* VOID:OBJECT,LONG (eggmarshalers.list:3) */ +extern void _egg_marshal_VOID__OBJECT_LONG (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +G_END_DECLS + +#endif /* ___egg_marshal_MARSHAL_H__ */ + diff --git a/plugins/systray/eggtraymanager.c b/plugins/systray/eggtraymanager.c new file mode 100644 index 0000000..1d15c5e --- /dev/null +++ b/plugins/systray/eggtraymanager.c @@ -0,0 +1,655 @@ +/* eggtraymanager.c + * Copyright (C) 2002 Anders Carlsson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include "eggtraymanager.h" +#include "eggmarshalers.h" + +//#define DEBUGPRN +#include "dbg.h" +/* Signals */ +enum +{ + TRAY_ICON_ADDED, + TRAY_ICON_REMOVED, + MESSAGE_SENT, + MESSAGE_CANCELLED, + LOST_SELECTION, + LAST_SIGNAL +}; + +typedef struct +{ + long id, len; + long remaining_len; + + long timeout; + Window window; + char *str; +} PendingMessage; + +static GObjectClass *parent_class = NULL; +static guint manager_signals[LAST_SIGNAL] = { 0 }; + +#define SYSTEM_TRAY_REQUEST_DOCK 0 +#define SYSTEM_TRAY_BEGIN_MESSAGE 1 +#define SYSTEM_TRAY_CANCEL_MESSAGE 2 + +static gboolean egg_tray_manager_check_running_xscreen (Screen *xscreen); + +static void egg_tray_manager_init (EggTrayManager *manager); +static void egg_tray_manager_class_init (EggTrayManagerClass *klass); + +static void egg_tray_manager_finalize (GObject *object); + +static void egg_tray_manager_unmanage (EggTrayManager *manager); + +GType +egg_tray_manager_get_type (void) +{ + static GType our_type = 0; + + if (our_type == 0) + { + static const GTypeInfo our_info = + { + sizeof (EggTrayManagerClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) egg_tray_manager_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (EggTrayManager), + 0, /* n_preallocs */ + (GInstanceInitFunc) egg_tray_manager_init + }; + + our_type = g_type_register_static (G_TYPE_OBJECT, "EggTrayManager", &our_info, 0); + } + + return our_type; + +} + +static void +egg_tray_manager_init (EggTrayManager *manager) +{ + manager->socket_table = g_hash_table_new (NULL, NULL); +} + +static void +egg_tray_manager_class_init (EggTrayManagerClass *klass) +{ + GObjectClass *gobject_class; + + parent_class = g_type_class_peek_parent (klass); + gobject_class = (GObjectClass *)klass; + + gobject_class->finalize = egg_tray_manager_finalize; + + manager_signals[TRAY_ICON_ADDED] = + g_signal_new ("tray_icon_added", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggTrayManagerClass, tray_icon_added), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GTK_TYPE_SOCKET); + + manager_signals[TRAY_ICON_REMOVED] = + g_signal_new ("tray_icon_removed", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggTrayManagerClass, tray_icon_removed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GTK_TYPE_SOCKET); + manager_signals[MESSAGE_SENT] = + g_signal_new ("message_sent", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggTrayManagerClass, message_sent), + NULL, NULL, + _egg_marshal_VOID__OBJECT_STRING_LONG_LONG, + G_TYPE_NONE, 4, + GTK_TYPE_SOCKET, + G_TYPE_STRING, + G_TYPE_LONG, + G_TYPE_LONG); + manager_signals[MESSAGE_CANCELLED] = + g_signal_new ("message_cancelled", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggTrayManagerClass, message_cancelled), + NULL, NULL, + _egg_marshal_VOID__OBJECT_LONG, + G_TYPE_NONE, 2, + GTK_TYPE_SOCKET, + G_TYPE_LONG); + manager_signals[LOST_SELECTION] = + g_signal_new ("lost_selection", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggTrayManagerClass, lost_selection), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + +} + +static void +egg_tray_manager_finalize (GObject *object) +{ + EggTrayManager *manager; + + manager = EGG_TRAY_MANAGER (object); + + egg_tray_manager_unmanage (manager); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +EggTrayManager * +egg_tray_manager_new (void) +{ + EggTrayManager *manager; + + manager = g_object_new (EGG_TYPE_TRAY_MANAGER, NULL); + + return manager; +} + +static gboolean +egg_tray_manager_plug_removed (GtkSocket *socket, + EggTrayManager *manager) +{ + Window *window; + + ENTER; + window = g_object_get_data (G_OBJECT (socket), "egg-tray-child-window"); + + g_hash_table_remove (manager->socket_table, GINT_TO_POINTER (*window)); + g_object_set_data (G_OBJECT (socket), "egg-tray-child-window", + NULL); + + g_signal_emit (manager, manager_signals[TRAY_ICON_REMOVED], 0, socket); + + /* This destroys the socket. */ + RET(FALSE); +} + +static gboolean +egg_tray_manager_socket_exposed (GtkWidget *widget, + GdkEventExpose *event, + gpointer user_data) +{ + ENTER; + gdk_window_clear_area (widget->window, + event->area.x, event->area.y, + event->area.width, event->area.height); + RET(FALSE); +} + + + +static void +egg_tray_manager_make_socket_transparent (GtkWidget *widget, + gpointer user_data) +{ + ENTER; + if (GTK_WIDGET_NO_WINDOW (widget)) + RET(); + gdk_window_set_back_pixmap (widget->window, NULL, TRUE); + RET(); +} + + + +static void +egg_tray_manager_socket_style_set (GtkWidget *widget, + GtkStyle *previous_style, + gpointer user_data) +{ + ENTER; + if (widget->window == NULL) + RET(); + egg_tray_manager_make_socket_transparent(widget, user_data); + RET(); +} + +static void +egg_tray_manager_handle_dock_request(EggTrayManager *manager, + XClientMessageEvent *xevent) +{ + GtkWidget *socket; + Window *window; + + ENTER; + socket = gtk_socket_new (); + gtk_widget_set_app_paintable (socket, TRUE); + gtk_widget_set_double_buffered (socket, FALSE); + gtk_widget_add_events (socket, GDK_EXPOSURE_MASK); + + g_signal_connect (socket, "realize", + G_CALLBACK (egg_tray_manager_make_socket_transparent), NULL); + g_signal_connect (socket, "expose_event", + G_CALLBACK (egg_tray_manager_socket_exposed), NULL); + g_signal_connect_after (socket, "style_set", + G_CALLBACK (egg_tray_manager_socket_style_set), NULL); + gtk_widget_show (socket); + + + /* We need to set the child window here + * so that the client can call _get functions + * in the signal handler + */ + window = g_new (Window, 1); + *window = xevent->data.l[2]; + DBG("plug window %lx\n", *window); + g_object_set_data_full (G_OBJECT (socket), "egg-tray-child-window", + window, g_free); + g_signal_emit(manager, manager_signals[TRAY_ICON_ADDED], 0, + socket); + /* Add the socket only if it's been attached */ + if (GTK_IS_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(socket)))) { + GtkRequisition req; + XWindowAttributes wa; + + DBG("socket has window. going on\n"); + gtk_socket_add_id(GTK_SOCKET (socket), xevent->data.l[2]); + g_signal_connect(socket, "plug_removed", + G_CALLBACK(egg_tray_manager_plug_removed), manager); + + gdk_error_trap_push(); + XGetWindowAttributes(GDK_DISPLAY(), *window, &wa); + if (gdk_error_trap_pop()) { + ERR("can't embed window %lx\n", xevent->data.l[2]); + goto error; + } + g_hash_table_insert(manager->socket_table, + GINT_TO_POINTER(xevent->data.l[2]), socket); + req.width = req.height = 1; + gtk_widget_size_request(socket, &req); + RET(); + } +error: + DBG("socket has NO window. destroy it\n"); + g_signal_emit(manager, manager_signals[TRAY_ICON_REMOVED], 0, + socket); + gtk_widget_destroy(socket); + RET(); +} + +static void +pending_message_free (PendingMessage *message) +{ + g_free (message->str); + g_free (message); +} + +static void +egg_tray_manager_handle_message_data (EggTrayManager *manager, + XClientMessageEvent *xevent) +{ + GList *p; + int len; + + /* Try to see if we can find the + * pending message in the list + */ + for (p = manager->messages; p; p = p->next) + { + PendingMessage *msg = p->data; + + if (xevent->window == msg->window) + { + /* Append the message */ + len = MIN (msg->remaining_len, 20); + + memcpy ((msg->str + msg->len - msg->remaining_len), + &xevent->data, len); + msg->remaining_len -= len; + + if (msg->remaining_len == 0) + { + GtkSocket *socket; + + socket = g_hash_table_lookup (manager->socket_table, GINT_TO_POINTER (msg->window)); + + if (socket) + { + g_signal_emit (manager, manager_signals[MESSAGE_SENT], 0, + socket, msg->str, msg->id, msg->timeout); + } + manager->messages = g_list_remove_link (manager->messages, + p); + + pending_message_free (msg); + } + + return; + } + } +} + +static void +egg_tray_manager_handle_begin_message (EggTrayManager *manager, + XClientMessageEvent *xevent) +{ + GList *p; + PendingMessage *msg; + + /* Check if the same message is + * already in the queue and remove it if so + */ + for (p = manager->messages; p; p = p->next) + { + PendingMessage *msg = p->data; + + if (xevent->window == msg->window && + xevent->data.l[4] == msg->id) + { + /* Hmm, we found it, now remove it */ + pending_message_free (msg); + manager->messages = g_list_remove_link (manager->messages, p); + break; + } + } + + /* Now add the new message to the queue */ + msg = g_new0 (PendingMessage, 1); + msg->window = xevent->window; + msg->timeout = xevent->data.l[2]; + msg->len = xevent->data.l[3]; + msg->id = xevent->data.l[4]; + msg->remaining_len = msg->len; + msg->str = g_malloc (msg->len + 1); + msg->str[msg->len] = '\0'; + manager->messages = g_list_prepend (manager->messages, msg); +} + +static void +egg_tray_manager_handle_cancel_message (EggTrayManager *manager, + XClientMessageEvent *xevent) +{ + GtkSocket *socket; + + socket = g_hash_table_lookup (manager->socket_table, GINT_TO_POINTER (xevent->window)); + + if (socket) + { + g_signal_emit (manager, manager_signals[MESSAGE_CANCELLED], 0, + socket, xevent->data.l[2]); + } +} + +static GdkFilterReturn +egg_tray_manager_handle_event (EggTrayManager *manager, + XClientMessageEvent *xevent) +{ + switch (xevent->data.l[1]) + { + case SYSTEM_TRAY_REQUEST_DOCK: + egg_tray_manager_handle_dock_request (manager, xevent); + return GDK_FILTER_REMOVE; + + case SYSTEM_TRAY_BEGIN_MESSAGE: + egg_tray_manager_handle_begin_message (manager, xevent); + return GDK_FILTER_REMOVE; + + case SYSTEM_TRAY_CANCEL_MESSAGE: + egg_tray_manager_handle_cancel_message (manager, xevent); + return GDK_FILTER_REMOVE; + default: + break; + } + + return GDK_FILTER_CONTINUE; +} + +static GdkFilterReturn +egg_tray_manager_window_filter (GdkXEvent *xev, GdkEvent *event, gpointer data) +{ + XEvent *xevent = (GdkXEvent *)xev; + EggTrayManager *manager = data; + + if (xevent->type == ClientMessage) + { + if (xevent->xclient.message_type == manager->opcode_atom) + { + return egg_tray_manager_handle_event (manager, (XClientMessageEvent *)xevent); + } + else if (xevent->xclient.message_type == manager->message_data_atom) + { + egg_tray_manager_handle_message_data (manager, (XClientMessageEvent *)xevent); + return GDK_FILTER_REMOVE; + } + } + else if (xevent->type == SelectionClear) + { + g_signal_emit (manager, manager_signals[LOST_SELECTION], 0); + egg_tray_manager_unmanage (manager); + } + + return GDK_FILTER_CONTINUE; +} + +static void +egg_tray_manager_unmanage (EggTrayManager *manager) +{ + Display *display; + guint32 timestamp; + GtkWidget *invisible; + + if (manager->invisible == NULL) + return; + + invisible = manager->invisible; + g_assert (GTK_IS_INVISIBLE (invisible)); + g_assert (GTK_WIDGET_REALIZED (invisible)); + g_assert (GDK_IS_WINDOW (invisible->window)); + + display = GDK_WINDOW_XDISPLAY (invisible->window); + + if (XGetSelectionOwner (display, manager->selection_atom) == + GDK_WINDOW_XWINDOW (invisible->window)) + { + timestamp = gdk_x11_get_server_time (invisible->window); + XSetSelectionOwner (display, manager->selection_atom, None, timestamp); + } + + gdk_window_remove_filter (invisible->window, egg_tray_manager_window_filter, manager); + + manager->invisible = NULL; /* prior to destroy for reentrancy paranoia */ + gtk_widget_destroy (invisible); + g_object_unref (G_OBJECT (invisible)); +} + +static gboolean +egg_tray_manager_manage_xscreen (EggTrayManager *manager, Screen *xscreen) +{ + GtkWidget *invisible; + char *selection_atom_name; + guint32 timestamp; + GdkScreen *screen; + + g_return_val_if_fail (EGG_IS_TRAY_MANAGER (manager), FALSE); + g_return_val_if_fail (manager->screen == NULL, FALSE); + + /* If there's already a manager running on the screen + * we can't create another one. + */ +#if 0 + if (egg_tray_manager_check_running_xscreen (xscreen)) + return FALSE; +#endif + screen = gdk_display_get_screen (gdk_x11_lookup_xdisplay (DisplayOfScreen (xscreen)), + XScreenNumberOfScreen (xscreen)); + + invisible = gtk_invisible_new_for_screen (screen); + gtk_widget_realize (invisible); + + gtk_widget_add_events (invisible, GDK_PROPERTY_CHANGE_MASK | GDK_STRUCTURE_MASK); + + selection_atom_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d", + XScreenNumberOfScreen (xscreen)); + manager->selection_atom = XInternAtom (DisplayOfScreen (xscreen), selection_atom_name, False); + + g_free (selection_atom_name); + + timestamp = gdk_x11_get_server_time (invisible->window); + XSetSelectionOwner (DisplayOfScreen (xscreen), manager->selection_atom, + GDK_WINDOW_XWINDOW (invisible->window), timestamp); + + /* Check if we were could set the selection owner successfully */ + if (XGetSelectionOwner (DisplayOfScreen (xscreen), manager->selection_atom) == + GDK_WINDOW_XWINDOW (invisible->window)) + { + XClientMessageEvent xev; + + xev.type = ClientMessage; + xev.window = RootWindowOfScreen (xscreen); + xev.message_type = XInternAtom (DisplayOfScreen (xscreen), "MANAGER", False); + + xev.format = 32; + xev.data.l[0] = timestamp; + xev.data.l[1] = manager->selection_atom; + xev.data.l[2] = GDK_WINDOW_XWINDOW (invisible->window); + xev.data.l[3] = 0; /* manager specific data */ + xev.data.l[4] = 0; /* manager specific data */ + + XSendEvent (DisplayOfScreen (xscreen), + RootWindowOfScreen (xscreen), + False, StructureNotifyMask, (XEvent *)&xev); + + manager->invisible = invisible; + g_object_ref (G_OBJECT (manager->invisible)); + + manager->opcode_atom = XInternAtom (DisplayOfScreen (xscreen), + "_NET_SYSTEM_TRAY_OPCODE", + False); + + manager->message_data_atom = XInternAtom (DisplayOfScreen (xscreen), + "_NET_SYSTEM_TRAY_MESSAGE_DATA", + False); + + /* Add a window filter */ + gdk_window_add_filter (invisible->window, egg_tray_manager_window_filter, manager); + return TRUE; + } + else + { + gtk_widget_destroy (invisible); + + return FALSE; + } +} + +gboolean +egg_tray_manager_manage_screen (EggTrayManager *manager, + GdkScreen *screen) +{ + g_return_val_if_fail (GDK_IS_SCREEN (screen), FALSE); + g_return_val_if_fail (manager->screen == NULL, FALSE); + + return egg_tray_manager_manage_xscreen (manager, + GDK_SCREEN_XSCREEN (screen)); +} + +static gboolean +egg_tray_manager_check_running_xscreen (Screen *xscreen) +{ + Atom selection_atom; + char *selection_atom_name; + + selection_atom_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d", + XScreenNumberOfScreen (xscreen)); + selection_atom = XInternAtom (DisplayOfScreen (xscreen), selection_atom_name, False); + g_free (selection_atom_name); + + if (XGetSelectionOwner (DisplayOfScreen (xscreen), selection_atom)) + return TRUE; + else + return FALSE; +} + +gboolean +egg_tray_manager_check_running (GdkScreen *screen) +{ + g_return_val_if_fail (GDK_IS_SCREEN (screen), FALSE); + + return egg_tray_manager_check_running_xscreen (GDK_SCREEN_XSCREEN (screen)); +} + +char * +egg_tray_manager_get_child_title (EggTrayManager *manager, + EggTrayManagerChild *child) +{ + Window *child_window; + Atom utf8_string, atom, type; + int result; + gchar *val, *retval; + int format; + gulong nitems; + gulong bytes_after; + guchar *tmp = NULL; + + g_return_val_if_fail (EGG_IS_TRAY_MANAGER (manager), NULL); + g_return_val_if_fail (GTK_IS_SOCKET (child), NULL); + + child_window = g_object_get_data (G_OBJECT (child), + "egg-tray-child-window"); + + utf8_string = XInternAtom (GDK_DISPLAY (), "UTF8_STRING", False); + atom = XInternAtom (GDK_DISPLAY (), "_NET_WM_NAME", False); + + gdk_error_trap_push(); + + result = XGetWindowProperty (GDK_DISPLAY (), *child_window, atom, 0, + G_MAXLONG, False, utf8_string, &type, &format, &nitems, + &bytes_after, &tmp); + val = (gchar *) tmp; + if (gdk_error_trap_pop() || result != Success || type != utf8_string) + return NULL; + + if (format != 8 || nitems == 0) { + if (val) + XFree (val); + return NULL; + } + + if (!g_utf8_validate (val, nitems, NULL)) + { + XFree (val); + return NULL; + } + + retval = g_strndup (val, nitems); + + XFree (val); + + return retval; + +} diff --git a/plugins/systray/eggtraymanager.h b/plugins/systray/eggtraymanager.h new file mode 100644 index 0000000..e2abdb6 --- /dev/null +++ b/plugins/systray/eggtraymanager.h @@ -0,0 +1,88 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* eggtraymanager.h + * Copyright (C) 2002 Anders Carlsson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __EGG_TRAY_MANAGER_H__ +#define __EGG_TRAY_MANAGER_H__ + +#include +#include + +G_BEGIN_DECLS + +#define EGG_TYPE_TRAY_MANAGER (egg_tray_manager_get_type ()) +#define EGG_TRAY_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_TRAY_MANAGER, EggTrayManager)) +#define EGG_TRAY_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_TRAY_MANAGER, EggTrayManagerClass)) +#define EGG_IS_TRAY_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_TRAY_MANAGER)) +#define EGG_IS_TRAY_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_TRAY_MANAGER)) +#define EGG_TRAY_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_TRAY_MANAGER, EggTrayManagerClass)) + +typedef struct _EggTrayManager EggTrayManager; +typedef struct _EggTrayManagerClass EggTrayManagerClass; +typedef struct _EggTrayManagerChild EggTrayManagerChild; + +struct _EggTrayManager +{ + GObject parent_instance; + + Atom opcode_atom; + Atom selection_atom; + Atom message_data_atom; + + GtkWidget *invisible; + GdkScreen *screen; + + GList *messages; + GHashTable *socket_table; +}; + +struct _EggTrayManagerClass +{ + GObjectClass parent_class; + + void (* tray_icon_added) (EggTrayManager *manager, + EggTrayManagerChild *child); + void (* tray_icon_removed) (EggTrayManager *manager, + EggTrayManagerChild *child); + + void (* message_sent) (EggTrayManager *manager, + EggTrayManagerChild *child, + const gchar *message, + glong id, + glong timeout); + + void (* message_cancelled) (EggTrayManager *manager, + EggTrayManagerChild *child, + glong id); + + void (* lost_selection) (EggTrayManager *manager); +}; + +GType egg_tray_manager_get_type (void); + +gboolean egg_tray_manager_check_running (GdkScreen *screen); +EggTrayManager *egg_tray_manager_new (void); +gboolean egg_tray_manager_manage_screen (EggTrayManager *manager, + GdkScreen *screen); +char *egg_tray_manager_get_child_title (EggTrayManager *manager, + EggTrayManagerChild *child); + +G_END_DECLS + +#endif /* __EGG_TRAY_MANAGER_H__ */ diff --git a/plugins/systray/fixedtip.c b/plugins/systray/fixedtip.c new file mode 100644 index 0000000..b07ce04 --- /dev/null +++ b/plugins/systray/fixedtip.c @@ -0,0 +1,158 @@ +/* Metacity fixed tooltip routine */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "fixedtip.h" + +static GtkWidget *tip = NULL; +static GtkWidget *label = NULL; +static int screen_width = 0; +static int screen_height = 0; + +static gboolean +button_press_handler (GtkWidget *tip, + GdkEvent *event, + void *data) +{ + fixed_tip_hide (); + + return FALSE; +} + +static gboolean +expose_handler (GtkTooltips *tooltips) +{ + gtk_paint_flat_box (tip->style, tip->window, + GTK_STATE_NORMAL, GTK_SHADOW_OUT, + NULL, tip, "tooltip", + 0, 0, -1, -1); + + return FALSE; +} + +void +fixed_tip_show (int screen_number, + int root_x, int root_y, + gboolean strut_is_vertical, + int strut, + const char *markup_text) +{ + int w, h; + + if (tip == NULL) + { + tip = gtk_window_new (GTK_WINDOW_POPUP); +#ifdef HAVE_GTK_MULTIHEAD + { + GdkScreen *gdk_screen; + + gdk_screen = gdk_display_get_screen (gdk_get_default_display (), + screen_number); + gtk_window_set_screen (GTK_WINDOW (tip), + gdk_screen); + screen_width = gdk_screen_get_width (gdk_screen); + screen_height = gdk_screen_get_height (gdk_screen); + } +#else + screen_width = gdk_screen_width (); + screen_height = gdk_screen_height (); +#endif + + gtk_widget_set_app_paintable (tip, TRUE); + //gtk_window_set_policy (GTK_WINDOW (tip), FALSE, FALSE, TRUE); + gtk_window_set_resizable(GTK_WINDOW (tip), FALSE); + gtk_widget_set_name (tip, "gtk-tooltips"); + gtk_container_set_border_width (GTK_CONTAINER (tip), 4); + + g_signal_connect (G_OBJECT (tip), + "expose_event", + G_CALLBACK (expose_handler), + NULL); + + gtk_widget_add_events (tip, GDK_BUTTON_PRESS_MASK); + + g_signal_connect (G_OBJECT (tip), + "button_press_event", + G_CALLBACK (button_press_handler), + NULL); + + label = gtk_label_new (NULL); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0.5, 0.5); + gtk_widget_show (label); + + gtk_container_add (GTK_CONTAINER (tip), label); + + g_signal_connect (G_OBJECT (tip), + "destroy", + G_CALLBACK (gtk_widget_destroyed), + &tip); + } + + gtk_label_set_markup (GTK_LABEL (label), markup_text); + + /* FIXME should also handle Xinerama here, just to be + * really cool + */ + gtk_window_get_size (GTK_WINDOW (tip), &w, &h); + + /* pad between panel and message window */ +#define PAD 5 + + if (strut_is_vertical) + { + if (strut > root_x) + root_x = strut + PAD; + else + root_x = strut - w - PAD; + + root_y -= h / 2; + } + else + { + if (strut > root_y) + root_y = strut + PAD; + else + root_y = strut - h - PAD; + + root_x -= w / 2; + } + + /* Push onscreen */ + if ((root_x + w) > screen_width) + root_x -= (root_x + w) - screen_width; + + if ((root_y + h) > screen_height) + root_y -= (root_y + h) - screen_height; + + gtk_window_move (GTK_WINDOW (tip), root_x, root_y); + + gtk_widget_show (tip); +} + +void +fixed_tip_hide (void) +{ + if (tip) + { + gtk_widget_destroy (tip); + tip = NULL; + } +} diff --git a/plugins/systray/fixedtip.h b/plugins/systray/fixedtip.h new file mode 100644 index 0000000..16ef500 --- /dev/null +++ b/plugins/systray/fixedtip.h @@ -0,0 +1,40 @@ +/* Fixed tooltip routine */ + +/* + * Copyright (C) 2001 Havoc Pennington, 2002 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef FIXED_TIP_H +#define FIXED_TIP_H + +#include +#include + +/* root_x, root_y are where the speech balloon should be + * "pointing" and the strut is the panel edge we should be + * alongside. + */ +void fixed_tip_show (int screen_number, + int root_x, int root_y, + gboolean strut_is_vertical, + int strut, + const char *markup_text); +void fixed_tip_hide (void); + + +#endif diff --git a/plugins/systray/libsystray.so b/plugins/systray/libsystray.so new file mode 100755 index 0000000000000000000000000000000000000000..ad28e1f8ba3efb74b2164dc848428c3c2eb013f4 GIT binary patch literal 137118 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 abQ`)|9`~HXI#}6Ps^#MRwAyYn;Qtpj59fdY literal 0 HcmV?d00001 diff --git a/plugins/systray/main.c b/plugins/systray/main.c new file mode 100644 index 0000000..013f45b --- /dev/null +++ b/plugins/systray/main.c @@ -0,0 +1,181 @@ +#include +#include +#include + +#include + +#include "panel.h" +#include "misc.h" +#include "plugin.h" +#include "bg.h" +#include "gtkbgbox.h" +#include "gtkbar.h" + +#include "eggtraymanager.h" +#include "fixedtip.h" + + +//#define DEBUGPRN +#include "dbg.h" + + +typedef struct { + plugin_instance plugin; + GtkWidget *box; + EggTrayManager *tray_manager; + FbBg *bg; + gulong sid; +} tray_priv; + +static void +tray_bg_changed(FbBg *bg, GtkWidget *widget) +{ + ENTER; + gtk_widget_set_size_request(widget, widget->allocation.width, + widget->allocation.height); + gtk_widget_hide(widget); + if (gtk_events_pending()) + gtk_main_iteration(); + gtk_widget_show(widget); + gtk_widget_set_size_request(widget, -1, -1); + RET(); +} + +static void +tray_added (EggTrayManager *manager, GtkWidget *icon, tray_priv *tr) +{ + ENTER; + gtk_box_pack_end(GTK_BOX(tr->box), icon, FALSE, FALSE, 0); + gtk_widget_show(icon); + gdk_display_sync(gtk_widget_get_display(icon)); + tray_bg_changed(NULL, tr->plugin.pwid); + RET(); +} + +static void +tray_removed (EggTrayManager *manager, GtkWidget *icon, tray_priv *tr) +{ + ENTER; + DBG("del icon\n"); + tray_bg_changed(NULL, tr->plugin.pwid); + RET(); +} + +static void +message_sent (EggTrayManager *manager, GtkWidget *icon, const char *text, + glong id, glong timeout, void *data) +{ + /* FIXME multihead */ + int x, y; + + ENTER; + gdk_window_get_origin (icon->window, &x, &y); + fixed_tip_show (0, x, y, FALSE, gdk_screen_height () - 50, text); + RET(); +} + +static void +message_cancelled (EggTrayManager *manager, GtkWidget *icon, glong id, + void *data) +{ + ENTER; + RET(); +} + +static void +tray_destructor(plugin_instance *p) +{ + tray_priv *tr = (tray_priv *) p; + + ENTER; + g_signal_handler_disconnect(tr->bg, tr->sid); + g_object_unref(tr->bg); + /* Make sure we drop the manager selection */ + if (tr->tray_manager) + g_object_unref(G_OBJECT(tr->tray_manager)); + fixed_tip_hide(); + RET(); +} + + +static void +tray_size_alloc(GtkWidget *widget, GtkAllocation *a, + tray_priv *tr) +{ + int dim, size; + + ENTER; + size = tr->plugin.panel->max_elem_height; + if (tr->plugin.panel->orientation == GTK_ORIENTATION_HORIZONTAL) + dim = a->height / size; + else + dim = a->width / size; + DBG("width=%d height=%d iconsize=%d -> dim=%d\n", + a->width, a->height, size, dim); + gtk_bar_set_dimension(GTK_BAR(tr->box), dim); + RET(); +} + + +static int +tray_constructor(plugin_instance *p) +{ + tray_priv *tr; + GdkScreen *screen; + GtkWidget *ali; + + ENTER; + tr = (tray_priv *) p; + class_get("tray"); + ali = gtk_alignment_new(0.5, 0.5, 0, 0); + g_signal_connect(G_OBJECT(ali), "size-allocate", + (GCallback) tray_size_alloc, tr); + gtk_container_set_border_width(GTK_CONTAINER(ali), 0); + gtk_container_add(GTK_CONTAINER(p->pwid), ali); + tr->box = gtk_bar_new(p->panel->orientation, 0, + p->panel->max_elem_height, p->panel->max_elem_height); + gtk_container_add(GTK_CONTAINER(ali), tr->box); + gtk_container_set_border_width(GTK_CONTAINER (tr->box), 0); + gtk_widget_show_all(ali); + tr->bg = fb_bg_get_for_display(); + tr->sid = g_signal_connect(tr->bg, "changed", + G_CALLBACK(tray_bg_changed), p->pwid); + + screen = gtk_widget_get_screen(p->panel->topgwin); + + if (egg_tray_manager_check_running(screen)) { + tr->tray_manager = NULL; + ERR("tray: another systray already running\n"); + RET(1); + } + tr->tray_manager = egg_tray_manager_new (); + if (!egg_tray_manager_manage_screen (tr->tray_manager, screen)) + g_printerr("tray: can't get the system tray manager selection\n"); + + g_signal_connect(tr->tray_manager, "tray_icon_added", + G_CALLBACK(tray_added), tr); + g_signal_connect(tr->tray_manager, "tray_icon_removed", + G_CALLBACK(tray_removed), tr); + g_signal_connect(tr->tray_manager, "message_sent", + G_CALLBACK(message_sent), tr); + g_signal_connect(tr->tray_manager, "message_cancelled", + G_CALLBACK(message_cancelled), tr); + + gtk_widget_show_all(tr->box); + RET(1); + +} + + +static plugin_class class = { + .count = 0, + .type = "tray", + .name = "System tray", + .version = "1.0", + .description = "System tray aka Notification Area", + .priv_size = sizeof(tray_priv), + + .constructor = tray_constructor, + .destructor = tray_destructor, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/taskbar/Makefile b/plugins/taskbar/Makefile new file mode 100644 index 0000000..705012e --- /dev/null +++ b/plugins/taskbar/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +taskbar_src = taskbar.c +taskbar_cflags = -DPLUGIN $(GTK2_CFLAGS) +taskbar_libs = $(GTK2_LIBS) +taskbar_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/taskbar/taskbar.c b/plugins/taskbar/taskbar.c new file mode 100644 index 0000000..507615f --- /dev/null +++ b/plugins/taskbar/taskbar.c @@ -0,0 +1,1553 @@ +#include +#include +#include +#include + +#include +#include +//#include + +#include +#include +#include + + + +#include "panel.h" +#include "misc.h" +#include "plugin.h" +#include "data/images/default.xpm" +#include "gtkbar.h" + +/* + * 2006.09.10 modified by Hong Jen Yee (PCMan) pcman.tw (AT) gmail.com + * Following features are added: + * 1. Add XUrgencyHint support. (Flashing task bar buttons, can be disabled) + */ + +//#define DEBUGPRN +#include "dbg.h" + +struct _taskbar; +typedef struct _task{ + struct _taskbar *tb; + Window win; + char *name, *iname; + GtkWidget *button, *label, *eb; + GtkWidget *image; + GdkPixbuf *pixbuf; + + int refcount; + XClassHint ch; + int pos_x; + int width; + guint desktop; + net_wm_state nws; + net_wm_window_type nwwt; + guint flash_timeout; + unsigned int focused:1; + unsigned int iconified:1; + unsigned int urgency:1; + unsigned int using_netwm_icon:1; + unsigned int flash:1; + unsigned int flash_state:1; +} task; + + + +typedef struct _taskbar{ + plugin_instance plugin; + Window *wins; + Window topxwin; + int win_num; + GHashTable *task_list; + GtkWidget *hbox, *bar, *space, *menu; + GdkPixbuf *gen_pixbuf; + GtkStateType normal_state; + GtkStateType focused_state; + int num_tasks; + int task_width; + int vis_task_num; + int req_width; + int hbox_width; + int spacing; + guint cur_desk; + task *focused; + task *ptk; + task *menutask; + char **desk_names; + int desk_namesno; + int desk_num; + guint dnd_activate; + int alloc_no; + + int iconsize; + int task_width_max; + int task_height_max; + int accept_skip_pager; + int show_iconified; + int show_mapped; + int show_all_desks; + int tooltips; + int icons_only; + int use_mouse_wheel; + int use_urgency_hint; + int discard_release_event; +} taskbar_priv; + + +static gchar *taskbar_rc = "style 'taskbar-style'\n" +"{\n" +"GtkWidget::focus-line-width = 0\n" +"GtkWidget::focus-padding = 0\n" +"GtkButton::default-border = { 0, 0, 0, 0 }\n" +"GtkButton::default-outside-border = { 0, 0, 0, 0 }\n" +"GtkButton::default_border = { 0, 0, 0, 0 }\n" +"GtkButton::default_outside_border = { 0, 0, 0, 0 }\n" +"}\n" +"widget '*.taskbar.*' style 'taskbar-style'"; + +static gboolean use_net_active=FALSE; + +#define DRAG_ACTIVE_DELAY 1000 + + + +#define TASK_WIDTH_MAX 200 +#define TASK_HEIGHT_MAX 28 +#define TASK_PADDING 4 +static void tk_display(taskbar_priv *tb, task *tk); +static void tb_propertynotify(taskbar_priv *tb, XEvent *ev); +static GdkFilterReturn tb_event_filter( XEvent *, GdkEvent *, taskbar_priv *); +static void taskbar_destructor(plugin_instance *p); + +static gboolean tk_has_urgency( task* tk ); + +static void tk_flash_window( task *tk ); +static void tk_unflash_window( task *tk ); +static void tk_raise_window( task *tk, guint32 time ); + +#define TASK_VISIBLE(tb, tk) \ + ((tk)->desktop == (tb)->cur_desk || (tk)->desktop == -1 /* 0xFFFFFFFF */ ) + +static int +task_visible(taskbar_priv *tb, task *tk) +{ + ENTER; + DBG("%lx: desktop=%d iconified=%d \n", tk->win, tk->desktop, tk->iconified); + RET( (tb->show_all_desks || tk->desktop == -1 + || (tk->desktop == tb->cur_desk)) + && ((tk->iconified && tb->show_iconified) + || (!tk->iconified && tb->show_mapped)) ); +} + +inline static int +accept_net_wm_state(net_wm_state *nws, int accept_skip_pager) +{ + ENTER; + DBG("accept_skip_pager=%d skip_taskbar=%d skip_pager=%d\n", + accept_skip_pager, + nws->skip_taskbar, + nws->skip_pager); + + RET(!(nws->skip_taskbar || (accept_skip_pager && nws->skip_pager))); +} + +inline static int +accept_net_wm_window_type(net_wm_window_type *nwwt) +{ + ENTER; + DBG("desktop=%d dock=%d splash=%d\n", + nwwt->desktop, nwwt->dock, nwwt->splash); + + RET(!(nwwt->desktop || nwwt->dock || nwwt->splash)); +} + + + +static void +tk_free_names(task *tk) +{ + ENTER; + if ((!tk->name) != (!tk->iname)) { + DBG("tk names partially allocated \ntk->name=%s\ntk->iname %s\n", + tk->name, tk->iname); + } + if (tk->name && tk->iname) { + g_free(tk->name); + g_free(tk->iname); + tk->name = tk->iname = NULL; + tk->tb->alloc_no--; + } + RET(); +} + +static void +tk_get_names(task *tk) +{ + char *name; + + ENTER; + tk_free_names(tk); + name = get_utf8_property(tk->win, a_NET_WM_NAME); + DBG("a_NET_WM_NAME:%s\n", name); + if (!name) { + name = get_textproperty(tk->win, XA_WM_NAME); + DBG("XA_WM_NAME:%s\n", name); + } + if (name) { + tk->name = g_strdup_printf(" %s ", name); + tk->iname = g_strdup_printf("[%s]", name); + g_free(name); + tk->tb->alloc_no++; + } + RET(); +} + +static void +tk_set_names(task *tk) +{ + char *name; + + ENTER; + name = tk->iconified ? tk->iname : tk->name; + if (!tk->tb->icons_only) + gtk_label_set_text(GTK_LABEL(tk->label), name); + if (tk->tb->tooltips) + gtk_widget_set_tooltip_text(tk->button, tk->name); + RET(); +} + + + +static task * +find_task (taskbar_priv * tb, Window win) +{ + ENTER; + RET(g_hash_table_lookup(tb->task_list, &win)); +} + + +static void +del_task (taskbar_priv * tb, task *tk, int hdel) +{ + ENTER; + DBG("deleting(%d) %08x %s\n", hdel, tk->win, tk->name); + if (tk->flash_timeout) + g_source_remove(tk->flash_timeout); + gtk_widget_destroy(tk->button); + tb->num_tasks--; + tk_free_names(tk); + if (tb->focused == tk) + tb->focused = NULL; + if (hdel) + g_hash_table_remove(tb->task_list, &tk->win); + g_free(tk); + RET(); +} + + + +static GdkColormap* +get_cmap (GdkPixmap *pixmap) +{ + GdkColormap *cmap; + + ENTER; + cmap = gdk_drawable_get_colormap (pixmap); + if (cmap) + g_object_ref (G_OBJECT (cmap)); + + if (cmap == NULL) + { + if (gdk_drawable_get_depth (pixmap) == 1) + { + /* try null cmap */ + cmap = NULL; + } + else + { + /* Try system cmap */ + GdkScreen *screen = gdk_drawable_get_screen (GDK_DRAWABLE (pixmap)); + cmap = gdk_screen_get_system_colormap (screen); + g_object_ref (G_OBJECT (cmap)); + } + } + + /* Be sure we aren't going to blow up due to visual mismatch */ + if (cmap && + (gdk_colormap_get_visual (cmap)->depth != + gdk_drawable_get_depth (pixmap))) + cmap = NULL; + + RET(cmap); +} + +static GdkPixbuf* +_wnck_gdk_pixbuf_get_from_pixmap (GdkPixbuf *dest, + Pixmap xpixmap, + int src_x, + int src_y, + int dest_x, + int dest_y, + int width, + int height) +{ + GdkDrawable *drawable; + GdkPixbuf *retval; + GdkColormap *cmap; + + ENTER; + retval = NULL; + + drawable = gdk_xid_table_lookup (xpixmap); + + if (drawable) + g_object_ref (G_OBJECT (drawable)); + else + drawable = gdk_pixmap_foreign_new (xpixmap); + + cmap = get_cmap (drawable); + + /* GDK is supposed to do this but doesn't in GTK 2.0.2, + * fixed in 2.0.3 + */ + if (width < 0) + gdk_drawable_get_size (drawable, &width, NULL); + if (height < 0) + gdk_drawable_get_size (drawable, NULL, &height); + + retval = gdk_pixbuf_get_from_drawable (dest, + drawable, + cmap, + src_x, src_y, + dest_x, dest_y, + width, height); + + if (cmap) + g_object_unref (G_OBJECT (cmap)); + g_object_unref (G_OBJECT (drawable)); + + RET(retval); +} + +static GdkPixbuf* +apply_mask (GdkPixbuf *pixbuf, + GdkPixbuf *mask) +{ + int w, h; + int i, j; + GdkPixbuf *with_alpha; + guchar *src; + guchar *dest; + int src_stride; + int dest_stride; + + ENTER; + w = MIN (gdk_pixbuf_get_width (mask), gdk_pixbuf_get_width (pixbuf)); + h = MIN (gdk_pixbuf_get_height (mask), gdk_pixbuf_get_height (pixbuf)); + + with_alpha = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0); + + dest = gdk_pixbuf_get_pixels (with_alpha); + src = gdk_pixbuf_get_pixels (mask); + + dest_stride = gdk_pixbuf_get_rowstride (with_alpha); + src_stride = gdk_pixbuf_get_rowstride (mask); + + i = 0; + while (i < h) + { + j = 0; + while (j < w) + { + guchar *s = src + i * src_stride + j * 3; + guchar *d = dest + i * dest_stride + j * 4; + + /* s[0] == s[1] == s[2], they are 255 if the bit was set, 0 + * otherwise + */ + if (s[0] == 0) + d[3] = 0; /* transparent */ + else + d[3] = 255; /* opaque */ + + ++j; + } + + ++i; + } + + RET(with_alpha); +} + + +static void +free_pixels (guchar *pixels, gpointer data) +{ + ENTER; + g_free (pixels); + RET(); +} + + +static guchar * +argbdata_to_pixdata (gulong *argb_data, int len) +{ + guchar *p, *ret; + int i; + + ENTER; + ret = p = g_new (guchar, len * 4); + if (!ret) + RET(NULL); + /* One could speed this up a lot. */ + i = 0; + while (i < len) { + guint32 argb; + guint32 rgba; + + argb = argb_data[i]; + rgba = (argb << 8) | (argb >> 24); + + *p = rgba >> 24; + ++p; + *p = (rgba >> 16) & 0xff; + ++p; + *p = (rgba >> 8) & 0xff; + ++p; + *p = rgba & 0xff; + ++p; + + ++i; + } + RET(ret); +} + +#if 0 +GdkPixbuf * +gdk_pixbuf_scale_ratio(GdkPixbuf *p, int width, int height, GdkInterpType itype) +{ + gfloat w, h, rw, rh; + + w = gdk_pixbuf_get_width(p); + h = gdk_pixbuf_get_height(p); + rw = w / width; + rh = h / height; + if (rw > rh) + height = h / rw; + else + width = w / rh; + return gdk_pixbuf_scale_simple(p, width, height, itype); + +} +#endif + + + +static GdkPixbuf * +get_netwm_icon(Window tkwin, int iw, int ih) +{ + gulong *data; + GdkPixbuf *ret = NULL; + int n; + guchar *p; + GdkPixbuf *src; + int w, h; + + ENTER; + data = get_xaproperty(tkwin, a_NET_WM_ICON, XA_CARDINAL, &n); + if (!data) + RET(NULL); + + /* loop through all icons in data to find best fit */ + if (0) { + gulong *tmp; + int len; + + len = n/sizeof(gulong); + tmp = data; + while (len > 2) { + int size = tmp[0] * tmp[1]; + DBG("sub-icon: %dx%d %d bytes\n", tmp[0], tmp[1], size * 4); + len -= size + 2; + tmp += size; + } + } + + if (0) { + int i, j, nn; + + nn = MIN(10, n); + p = (guchar *) data; + for (i = 0; i < nn; i++) { + for (j = 0; j < sizeof(gulong); j++) + ERR("%02x ", (guint) p[i*sizeof(gulong) + j]); + ERR("\n"); + } + } + + /* check that data indeed represents icon in w + h + ARGB[] format + * with 16x16 dimension at least */ + if (n < (16 * 16 + 1 + 1)) { + ERR("win %lx: icon is too small or broken (size=%d)\n", tkwin, n); + goto out; + } + w = data[0]; + h = data[1]; + /* check that sizes are in 64-256 range */ + if (w < 16 || w > 256 || h < 16 || h > 256) { + ERR("win %lx: icon size (%d, %d) is not in 64-256 range\n", + tkwin, w, h); + goto out; + } + + DBG("orig %dx%d dest %dx%d\n", w, h, iw, ih); + p = argbdata_to_pixdata(data + 2, w * h); + if (!p) + goto out; + src = gdk_pixbuf_new_from_data (p, GDK_COLORSPACE_RGB, TRUE, + 8, w, h, w * 4, free_pixels, NULL); + if (src == NULL) + goto out; + ret = src; + if (w != iw || h != ih) { + ret = gdk_pixbuf_scale_simple(src, iw, ih, GDK_INTERP_HYPER); + g_object_unref(src); + } + +out: + XFree(data); + RET(ret); +} + +static GdkPixbuf * +get_wm_icon(Window tkwin, int iw, int ih) +{ + XWMHints *hints; + Pixmap xpixmap = None, xmask = None; + Window win; + unsigned int w, h; + int sd; + GdkPixbuf *ret, *masked, *pixmap, *mask = NULL; + + ENTER; + hints = XGetWMHints(GDK_DISPLAY(), tkwin); + DBG("\nwm_hints %s\n", hints ? "ok" : "failed"); + if (!hints) + RET(NULL); + + if ((hints->flags & IconPixmapHint)) + xpixmap = hints->icon_pixmap; + if ((hints->flags & IconMaskHint)) + xmask = hints->icon_mask; + DBG("flag=%ld xpixmap=%lx flag=%ld xmask=%lx\n", + (hints->flags & IconPixmapHint), xpixmap, + (hints->flags & IconMaskHint), xmask); + XFree(hints); + if (xpixmap == None) + RET(NULL); + + if (!XGetGeometry (GDK_DISPLAY(), xpixmap, &win, &sd, &sd, &w, &h, + (guint *)&sd, (guint *)&sd)) { + DBG("XGetGeometry failed for %x pixmap\n", (unsigned int)xpixmap); + RET(NULL); + } + DBG("tkwin=%x icon pixmap w=%d h=%d\n", tkwin, w, h); + pixmap = _wnck_gdk_pixbuf_get_from_pixmap (NULL, xpixmap, 0, 0, 0, 0, w, h); + if (!pixmap) + RET(NULL); + if (xmask != None && XGetGeometry (GDK_DISPLAY(), xmask, + &win, &sd, &sd, &w, &h, (guint *)&sd, (guint *)&sd)) { + mask = _wnck_gdk_pixbuf_get_from_pixmap (NULL, xmask, 0, 0, 0, 0, w, h); + + if (mask) { + masked = apply_mask (pixmap, mask); + g_object_unref (G_OBJECT (pixmap)); + g_object_unref (G_OBJECT (mask)); + pixmap = masked; + } + } + if (!pixmap) + RET(NULL); + ret = gdk_pixbuf_scale_simple (pixmap, iw, ih, GDK_INTERP_TILES); + g_object_unref(pixmap); + + RET(ret); +} + +inline static GdkPixbuf* +get_generic_icon(taskbar_priv *tb) +{ + ENTER; + g_object_ref(tb->gen_pixbuf); + RET(tb->gen_pixbuf); +} + +static void +tk_update_icon (taskbar_priv *tb, task *tk, Atom a) +{ + GdkPixbuf *pixbuf; + + ENTER; + DBG("%lx: ", tk->win); + pixbuf = tk->pixbuf; + if (a == a_NET_WM_ICON || a == None) { + tk->pixbuf = get_netwm_icon(tk->win, tb->iconsize, tb->iconsize); + tk->using_netwm_icon = (tk->pixbuf != NULL); + DBGE("netwm_icon=%d ", tk->using_netwm_icon); + } + if (!tk->using_netwm_icon) { + tk->pixbuf = get_wm_icon(tk->win, tb->iconsize, tb->iconsize); + DBGE("wm_icon=%d ", (tk->pixbuf != NULL)); + } + if (!tk->pixbuf) { + tk->pixbuf = get_generic_icon(tb); // always exists + DBGE("generic_icon=1"); + } + if (pixbuf != tk->pixbuf) { + if (pixbuf) + g_object_unref(pixbuf); + } + DBGE(" %dx%d \n", gdk_pixbuf_get_width(tk->pixbuf), + gdk_pixbuf_get_height(tk->pixbuf)); + RET(); +} + +static gboolean +on_flash_win( task *tk ) +{ + tk->flash_state = !tk->flash_state; + gtk_widget_set_state(tk->button, + tk->flash_state ? GTK_STATE_SELECTED : tk->tb->normal_state); + gtk_widget_queue_draw(tk->button); + return TRUE; +} + +static void +tk_flash_window( task *tk ) +{ + gint interval; + tk->flash = 1; + tk->flash_state = !tk->flash_state; + if (tk->flash_timeout) + return; + g_object_get( gtk_widget_get_settings(tk->button), + "gtk-cursor-blink-time", &interval, NULL ); + tk->flash_timeout = g_timeout_add(interval, (GSourceFunc)on_flash_win, tk); +} + +static void +tk_unflash_window( task *tk ) +{ + tk->flash = tk->flash_state = 0; + if (tk->flash_timeout) { + g_source_remove(tk->flash_timeout); + tk->flash_timeout = 0; + } +} + +static void +tk_raise_window( task *tk, guint32 time ) +{ + if (tk->desktop != -1 && tk->desktop != tk->tb->cur_desk){ + Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, tk->desktop, + 0, 0, 0, 0); + XSync (gdk_display, False); + } + if(use_net_active) { + Xclimsg(tk->win, a_NET_ACTIVE_WINDOW, 2, time, 0, 0, 0); + } + else { + XRaiseWindow (GDK_DISPLAY(), tk->win); + XSetInputFocus (GDK_DISPLAY(), tk->win, RevertToNone, CurrentTime); + } + DBG("XRaiseWindow %x\n", tk->win); +} + +static void +tk_callback_leave( GtkWidget *widget, task *tk) +{ + ENTER; + gtk_widget_set_state(widget, + (tk->focused) ? tk->tb->focused_state : tk->tb->normal_state); + RET(); +} + + +static void +tk_callback_enter( GtkWidget *widget, task *tk ) +{ + ENTER; + gtk_widget_set_state(widget, + (tk->focused) ? tk->tb->focused_state : tk->tb->normal_state); + RET(); +} + +static gboolean +delay_active_win(task* tk) +{ + tk_raise_window(tk, CurrentTime); + tk->tb->dnd_activate = 0; + return FALSE; +} + +static gboolean +tk_callback_drag_motion( GtkWidget *widget, + GdkDragContext *drag_context, + gint x, gint y, + guint time, task *tk) +{ + /* prevent excessive motion notification */ + if (!tk->tb->dnd_activate) { + tk->tb->dnd_activate = g_timeout_add(DRAG_ACTIVE_DELAY, + (GSourceFunc)delay_active_win, tk); + } + gdk_drag_status (drag_context,0,time); + return TRUE; +} + +static void +tk_callback_drag_leave (GtkWidget *widget, + GdkDragContext *drag_context, + guint time, task *tk) +{ + if (tk->tb->dnd_activate) { + g_source_remove(tk->tb->dnd_activate); + tk->tb->dnd_activate = 0; + } + return; +} + +#if 0 +static gboolean +tk_callback_expose(GtkWidget *widget, GdkEventExpose *event, task *tk) +{ + GtkStateType state; + ENTER; + state = (tk->focused) ? tk->tb->focused_state : tk->tb->normal_state; + if (GTK_WIDGET_STATE(widget) != state) { + gtk_widget_set_state(widget, state); + gtk_widget_queue_draw(widget); + } else { + if( ! tk->flash || 0 == tk->flash_state ) { + gtk_paint_box (widget->style, widget->window, + state, + (tk->focused) ? GTK_SHADOW_IN : GTK_SHADOW_OUT, + &event->area, widget, "button", + widget->allocation.x, widget->allocation.y, + widget->allocation.width, widget->allocation.height); + } else { + gdk_draw_rectangle( widget->window, + widget->style->bg_gc[GTK_STATE_SELECTED], + TRUE, 0, 0, + widget->allocation.width, + widget->allocation.height ); + } + /* + _gtk_button_paint(GTK_BUTTON(widget), &event->area, state, + (tk->focused) ? GTK_SHADOW_IN : GTK_SHADOW_OUT, + "button", "buttondefault"); + */ + gtk_container_propagate_expose(GTK_CONTAINER(widget), + GTK_BIN(widget)->child, event); + } + RET(FALSE); +} +#endif + +static gint +tk_callback_scroll_event (GtkWidget *widget, GdkEventScroll *event, task *tk) +{ + ENTER; + if (event->direction == GDK_SCROLL_UP) { + GdkWindow *gdkwindow; + + gdkwindow = gdk_xid_table_lookup (tk->win); + if (gdkwindow) + gdk_window_show (gdkwindow); + else + XMapRaised (GDK_DISPLAY(), tk->win); + XSetInputFocus (GDK_DISPLAY(), tk->win, RevertToNone, CurrentTime); + DBG("XMapRaised %x\n", tk->win); + } else if (event->direction == GDK_SCROLL_DOWN) { + DBG("tb->ptk = %x\n", (tk->tb->ptk) ? tk->tb->ptk->win : 0); + XIconifyWindow (GDK_DISPLAY(), tk->win, DefaultScreen(GDK_DISPLAY())); + DBG("XIconifyWindow %x\n", tk->win); + } + + XSync (gdk_display, False); + RET(TRUE); +} + + +static gboolean +tk_callback_button_press_event(GtkWidget *widget, GdkEventButton *event, + task *tk) +{ + ENTER; + if (event->type == GDK_BUTTON_PRESS && event->button == 3 + && event->state & GDK_CONTROL_MASK) { + tk->tb->discard_release_event = 1; + gtk_propagate_event(tk->tb->bar, (GdkEvent *)event); + RET(TRUE); + } + RET(FALSE); +} + + +static gboolean +tk_callback_button_release_event(GtkWidget *widget, GdkEventButton *event, + task *tk) +{ + ENTER; + + if (event->type == GDK_BUTTON_RELEASE && tk->tb->discard_release_event) { + tk->tb->discard_release_event = 0; + RET(TRUE); + } + if ((event->type != GDK_BUTTON_RELEASE) || (!GTK_BUTTON(widget)->in_button)) + RET(FALSE); + DBG("win=%x\n", tk->win); + if (event->button == 1) { + if (tk->iconified) { + if(use_net_active) { + Xclimsg(tk->win, a_NET_ACTIVE_WINDOW, 2, event->time, 0, 0, 0); + } else { + GdkWindow *gdkwindow; + + gdkwindow = gdk_xid_table_lookup (tk->win); + if (gdkwindow) + gdk_window_show (gdkwindow); + else + XMapRaised (GDK_DISPLAY(), tk->win); + XSync (GDK_DISPLAY(), False); + DBG("XMapRaised %x\n", tk->win); + } + } else { + DBG("tb->ptk = %x\n", (tk->tb->ptk) ? tk->tb->ptk->win : 0); + if (tk->focused || tk == tk->tb->ptk) { + //tk->iconified = 1; + XIconifyWindow (GDK_DISPLAY(), tk->win, + DefaultScreen(GDK_DISPLAY())); + DBG("XIconifyWindow %x\n", tk->win); + } else { + tk_raise_window( tk, event->time ); + } + } + } else if (event->button == 2) { + Xclimsg(tk->win, a_NET_WM_STATE, + 2 /*a_NET_WM_STATE_TOGGLE*/, + a_NET_WM_STATE_SHADED, + 0, 0, 0); + } else if (event->button == 3) { + /* + XLowerWindow (GDK_DISPLAY(), tk->win); + DBG("XLowerWindow %x\n", tk->win); + */ + tk->tb->menutask = tk; + gtk_menu_popup (GTK_MENU (tk->tb->menu), NULL, NULL, + (GtkMenuPositionFunc)menu_pos, + tk->tb->plugin.panel->orientation == GTK_ORIENTATION_HORIZONTAL + ? NULL : widget, + event->button, event->time); + + } + gtk_button_released(GTK_BUTTON(widget)); + XSync (gdk_display, False); + RET(TRUE); +} + + +static void +tk_update(gpointer key, task *tk, taskbar_priv *tb) +{ + ENTER; + g_assert ((tb != NULL) && (tk != NULL)); + if (task_visible(tb, tk)) { + gtk_widget_set_state (tk->button, + (tk->focused) ? tb->focused_state : tb->normal_state); + gtk_widget_queue_draw(tk->button); + //_gtk_button_set_depressed(GTK_BUTTON(tk->button), tk->focused); + gtk_widget_show(tk->button); + + if (tb->tooltips) { + gtk_widget_set_tooltip_text(tk->button, tk->name); + } + RET(); + } + gtk_widget_hide(tk->button); + RET(); +} + +static void +tk_display(taskbar_priv *tb, task *tk) +{ + ENTER; + tk_update(NULL, tk, tb); + RET(); +} + +static void +tb_display(taskbar_priv *tb) +{ + ENTER; + if (tb->wins) + g_hash_table_foreach(tb->task_list, (GHFunc) tk_update, (gpointer) tb); + RET(); + +} + +static void +tk_build_gui(taskbar_priv *tb, task *tk) +{ + GtkWidget *w1; + + ENTER; + g_assert ((tb != NULL) && (tk != NULL)); + + /* NOTE + * 1. the extended mask is sum of taskbar and pager needs + * see bug [ 940441 ] pager loose track of windows + * + * Do not change event mask to gtk windows spwaned by this gtk client + * this breaks gtk internals */ + if (!FBPANEL_WIN(tk->win)) + XSelectInput(GDK_DISPLAY(), tk->win, + PropertyChangeMask | StructureNotifyMask); + + /* button */ + tk->button = gtk_button_new(); + gtk_button_set_alignment(GTK_BUTTON(tk->button), 0.5, 0.5); + gtk_widget_show(tk->button); + gtk_container_set_border_width(GTK_CONTAINER(tk->button), 0); + gtk_widget_add_events (tk->button, GDK_BUTTON_RELEASE_MASK + | GDK_BUTTON_PRESS_MASK); + g_signal_connect(G_OBJECT(tk->button), "button_release_event", + G_CALLBACK(tk_callback_button_release_event), (gpointer)tk); + g_signal_connect(G_OBJECT(tk->button), "button_press_event", + G_CALLBACK(tk_callback_button_press_event), (gpointer)tk); + g_signal_connect_after (G_OBJECT (tk->button), "leave", + G_CALLBACK (tk_callback_leave), (gpointer) tk); + g_signal_connect_after (G_OBJECT (tk->button), "enter", + G_CALLBACK (tk_callback_enter), (gpointer) tk); + gtk_drag_dest_set( tk->button, 0, NULL, 0, 0); + g_signal_connect (G_OBJECT (tk->button), "drag-motion", + G_CALLBACK (tk_callback_drag_motion), (gpointer) tk); + g_signal_connect (G_OBJECT (tk->button), "drag-leave", + G_CALLBACK (tk_callback_drag_leave), (gpointer) tk); + if (tb->use_mouse_wheel) + g_signal_connect_after(G_OBJECT(tk->button), "scroll-event", + G_CALLBACK(tk_callback_scroll_event), (gpointer)tk); + + /* pix */ + tk_update_icon(tb, tk, None); + w1 = tk->image = gtk_image_new_from_pixbuf(tk->pixbuf); + gtk_misc_set_alignment(GTK_MISC(tk->image), 0.5, 0.5); + gtk_misc_set_padding(GTK_MISC(tk->image), 0, 0); + + if (!tb->icons_only) { + w1 = gtk_hbox_new(FALSE, 1); + gtk_container_set_border_width(GTK_CONTAINER(w1), 0); + gtk_box_pack_start(GTK_BOX(w1), tk->image, FALSE, FALSE, 0); + tk->label = gtk_label_new(tk->iconified ? tk->iname : tk->name); + gtk_label_set_ellipsize(GTK_LABEL(tk->label), PANGO_ELLIPSIZE_END); + gtk_misc_set_alignment(GTK_MISC(tk->label), 0.0, 0.5); + gtk_misc_set_padding(GTK_MISC(tk->label), 0, 0); + gtk_box_pack_start(GTK_BOX(w1), tk->label, TRUE, TRUE, 0); + } + + gtk_container_add (GTK_CONTAINER (tk->button), w1); + gtk_box_pack_start(GTK_BOX(tb->bar), tk->button, FALSE, TRUE, 0); + GTK_WIDGET_UNSET_FLAGS (tk->button, GTK_CAN_FOCUS); + GTK_WIDGET_UNSET_FLAGS (tk->button, GTK_CAN_DEFAULT); + + gtk_widget_show_all(tk->button); + if (!task_visible(tb, tk)) { + gtk_widget_hide(tk->button); + } + + if (tk->urgency) { + /* Flash button for window with urgency hint */ + tk_flash_window(tk); + } + RET(); +} + +static gboolean +task_remove_every(Window *win, task *tk) +{ + ENTER; + del_task(tk->tb, tk, 0); + RET(TRUE); +} + +/* tell to remove element with zero refcount */ +static gboolean +task_remove_stale(Window *win, task *tk, gpointer data) +{ + ENTER; + if (tk->refcount-- == 0) { + //DBG("tb_net_list : 0x%x %s\n", tk->win, tk->name); + del_task(tk->tb, tk, 0); + RET(TRUE); + } + RET(FALSE); +} + +/***************************************************** + * handlers for NET actions * + *****************************************************/ + + +static void +tb_net_client_list(GtkWidget *widget, taskbar_priv *tb) +{ + int i; + task *tk; + + ENTER; + if (tb->wins) + XFree(tb->wins); + tb->wins = get_xaproperty (GDK_ROOT_WINDOW(), + a_NET_CLIENT_LIST, XA_WINDOW, &tb->win_num); + if (!tb->wins) + RET(); + for (i = 0; i < tb->win_num; i++) { + if ((tk = g_hash_table_lookup(tb->task_list, &tb->wins[i]))) { + tk->refcount++; + } else { + net_wm_window_type nwwt; + net_wm_state nws; + + get_net_wm_state(tb->wins[i], &nws); + if (!accept_net_wm_state(&nws, tb->accept_skip_pager)) + continue; + get_net_wm_window_type(tb->wins[i], &nwwt); + if (!accept_net_wm_window_type(&nwwt)) + continue; + + tk = g_new0(task, 1); + tk->refcount = 1; + tb->num_tasks++; + tk->win = tb->wins[i]; + tk->tb = tb; + tk->iconified = nws.hidden; + tk->desktop = get_net_wm_desktop(tk->win); + tk->nws = nws; + tk->nwwt = nwwt; + if( tb->use_urgency_hint && tk_has_urgency(tk)) { + tk->urgency = 1; + } + + tk_build_gui(tb, tk); + tk_get_names(tk); + tk_set_names(tk); + + g_hash_table_insert(tb->task_list, &tk->win, tk); + DBG("adding %08x(%p) %s\n", tk->win, + FBPANEL_WIN(tk->win), tk->name); + } + } + + /* remove windows that arn't in the NET_CLIENT_LIST anymore */ + g_hash_table_foreach_remove(tb->task_list, (GHRFunc) task_remove_stale, + NULL); + tb_display(tb); + RET(); +} + + + +static void +tb_net_current_desktop(GtkWidget *widget, taskbar_priv *tb) +{ + ENTER; + tb->cur_desk = get_net_current_desktop(); + tb_display(tb); + RET(); +} + + +static void +tb_net_number_of_desktops(GtkWidget *widget, taskbar_priv *tb) +{ + ENTER; + tb->desk_num = get_net_number_of_desktops(); + tb_display(tb); + RET(); +} + + +/* set new active window. if that happens to be us, then remeber + * current focus to use it for iconify command */ +static void +tb_net_active_window(GtkWidget *widget, taskbar_priv *tb) +{ + Window *f; + task *ntk, *ctk; + int drop_old, make_new; + + ENTER; + g_assert (tb != NULL); + drop_old = make_new = 0; + ctk = tb->focused; + ntk = NULL; + f = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_ACTIVE_WINDOW, XA_WINDOW, 0); + DBG("FOCUS=%x\n", f ? *f : 0); + if (!f) { + drop_old = 1; + tb->ptk = NULL; + } else { + if (*f == tb->topxwin) { + if (ctk) { + tb->ptk = ctk; + drop_old = 1; + } + } else { + tb->ptk = NULL; + ntk = find_task(tb, *f); + if (ntk != ctk) { + drop_old = 1; + make_new = 1; + } + } + XFree(f); + } + if (ctk && drop_old) { + ctk->focused = 0; + tb->focused = NULL; + tk_display(tb, ctk); + DBG("old focus was dropped\n"); + } + if (ntk && make_new) { + ntk->focused = 1; + tb->focused = ntk; + tk_display(tb, ntk); + DBG("new focus was set\n"); + } + RET(); +} + +/* For older Xlib headers */ +#ifndef XUrgencyHint +#define XUrgencyHint (1 << 8) +#endif + +static gboolean +tk_has_urgency( task* tk ) +{ + XWMHints* hints; + + tk->urgency = 0; + hints = XGetWMHints(GDK_DISPLAY(), tk->win); + if (hints) { + if (hints->flags & XUrgencyHint) /* Got urgency hint */ + tk->urgency = 1; + XFree( hints ); + } + return tk->urgency; +} + +static void +tb_propertynotify(taskbar_priv *tb, XEvent *ev) +{ + Atom at; + Window win; + + ENTER; + DBG("win=%x\n", ev->xproperty.window); + at = ev->xproperty.atom; + win = ev->xproperty.window; + if (win != GDK_ROOT_WINDOW()) { + task *tk = find_task(tb, win); + + if (!tk) RET(); + DBG("win=%x\n", ev->xproperty.window); + if (at == a_NET_WM_DESKTOP) { + DBG("NET_WM_DESKTOP\n"); + tk->desktop = get_net_wm_desktop(win); + tb_display(tb); + } else if (at == XA_WM_NAME) { + DBG("WM_NAME\n"); + tk_get_names(tk); + tk_set_names(tk); + } else if (at == XA_WM_HINTS) { + /* some windows set their WM_HINTS icon after mapping */ + DBG("XA_WM_HINTS\n"); + tk_update_icon (tb, tk, XA_WM_HINTS); + gtk_image_set_from_pixbuf (GTK_IMAGE(tk->image), tk->pixbuf); + if (tb->use_urgency_hint) { + if (tk_has_urgency(tk)) { + //tk->urgency = 1; + tk_flash_window(tk); + } else { + //tk->urgency = 0; + tk_unflash_window(tk); + } + } + } else if (at == a_NET_WM_STATE) { + net_wm_state nws; + + DBG("_NET_WM_STATE\n"); + get_net_wm_state(tk->win, &nws); + if (!accept_net_wm_state(&nws, tb->accept_skip_pager)) { + del_task(tb, tk, 1); + tb_display(tb); + } else { + tk->iconified = nws.hidden; + tk_set_names(tk); + } + } else if (at == a_NET_WM_ICON) { + DBG("_NET_WM_ICON\n"); + DBG("#0 %d\n", GDK_IS_PIXBUF (tk->pixbuf)); + tk_update_icon (tb, tk, a_NET_WM_ICON); + DBG("#1 %d\n", GDK_IS_PIXBUF (tk->pixbuf)); + gtk_image_set_from_pixbuf (GTK_IMAGE(tk->image), tk->pixbuf); + DBG("#2 %d\n", GDK_IS_PIXBUF (tk->pixbuf)); + } else if (at == a_NET_WM_WINDOW_TYPE) { + net_wm_window_type nwwt; + + DBG("_NET_WM_WINDOW_TYPE\n"); + get_net_wm_window_type(tk->win, &nwwt); + if (!accept_net_wm_window_type(&nwwt)) { + del_task(tb, tk, 1); + tb_display(tb); + } + } else { + DBG("at = %d\n", at); + } + } + RET(); +} + +static GdkFilterReturn +tb_event_filter( XEvent *xev, GdkEvent *event, taskbar_priv *tb) +{ + + ENTER; + //RET(GDK_FILTER_CONTINUE); + g_assert(tb != NULL); + if (xev->type == PropertyNotify ) + tb_propertynotify(tb, xev); + RET(GDK_FILTER_CONTINUE); +} + +static void +menu_close_window(GtkWidget *widget, taskbar_priv *tb) +{ + ENTER; + DBG("win %x\n", tb->menutask->win); + XSync (GDK_DISPLAY(), 0); + //XKillClient(GDK_DISPLAY(), tb->menutask->win); + Xclimsgwm(tb->menutask->win, a_WM_PROTOCOLS, a_WM_DELETE_WINDOW); + XSync (GDK_DISPLAY(), 0); + RET(); +} + + +static void +menu_raise_window(GtkWidget *widget, taskbar_priv *tb) +{ + ENTER; + DBG("win %x\n", tb->menutask->win); + XMapRaised(GDK_DISPLAY(), tb->menutask->win); + RET(); +} + + +static void +menu_iconify_window(GtkWidget *widget, taskbar_priv *tb) +{ + ENTER; + DBG("win %x\n", tb->menutask->win); + XIconifyWindow (GDK_DISPLAY(), tb->menutask->win, + DefaultScreen(GDK_DISPLAY())); + RET(); +} + +static void +send_to_workspace(GtkWidget *widget, void *iii, taskbar_priv *tb) +{ + int dst_desktop; + + ENTER; + + dst_desktop = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "num")); + DBG("win %x -> %d\n", (unsigned int)tb->menutask->win, dst_desktop); + Xclimsg(tb->menutask->win, a_NET_WM_DESKTOP, dst_desktop, 0, 0, 0, 0); + + RET(); +} + +#define ALL_WORKSPACES 0xFFFFFFFF + +static void +tb_update_desktops_names(taskbar_priv *tb) +{ + ENTER; + tb->desk_namesno = get_net_number_of_desktops(); + if (tb->desk_names) + g_strfreev(tb->desk_names); + tb->desk_names = get_utf8_property_list(GDK_ROOT_WINDOW(), + a_NET_DESKTOP_NAMES, &(tb->desk_namesno)); + RET(); +} + +static void +tb_make_menu(GtkWidget *widget, taskbar_priv *tb) +{ + GtkWidget *mi, *menu, *submenu; + gchar *buf; + int i; + + ENTER; + menu = gtk_menu_new (); + + mi = gtk_image_menu_item_new_with_label (_("Raise")); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), + gtk_image_new_from_stock(GTK_STOCK_GO_UP, GTK_ICON_SIZE_MENU)); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); + g_signal_connect(G_OBJECT(mi), "activate", + (GCallback)menu_raise_window, tb); + gtk_widget_show (mi); + + mi = gtk_image_menu_item_new_with_label (_("Iconify")); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), + gtk_image_new_from_stock(GTK_STOCK_UNDO, GTK_ICON_SIZE_MENU)); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); + g_signal_connect(G_OBJECT(mi), "activate", + (GCallback)menu_iconify_window, tb); + gtk_widget_show (mi); + + tb_update_desktops_names(tb); + submenu = gtk_menu_new(); + for (i = 0; i < tb->desk_num; i++) { + buf = g_strdup_printf("%d %s", i + 1, + (i < tb->desk_namesno) ? tb->desk_names[i] : ""); + mi = gtk_image_menu_item_new_with_label (buf); + g_object_set_data(G_OBJECT(mi), "num", GINT_TO_POINTER(i)); + gtk_menu_shell_append (GTK_MENU_SHELL (submenu), mi); + g_signal_connect(G_OBJECT(mi), "button_press_event", + (GCallback)send_to_workspace, tb); + g_free(buf); + } + mi = gtk_image_menu_item_new_with_label(_("All workspaces")); + g_object_set_data(G_OBJECT(mi), "num", GINT_TO_POINTER(ALL_WORKSPACES)); + g_signal_connect(mi, "activate", + (GCallback)send_to_workspace, tb); + gtk_menu_shell_append (GTK_MENU_SHELL (submenu), mi); + gtk_widget_show_all(submenu); + + mi = gtk_image_menu_item_new_with_label(_("Move to workspace")); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), submenu); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), + gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU)); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); + gtk_widget_show (mi); + + mi = gtk_separator_menu_item_new(); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); + gtk_widget_show (mi); + + /* we want this item to be farest from mouse pointer */ + //mi = gtk_menu_item_new_with_label ("Close Window"); + mi = gtk_image_menu_item_new_from_stock(GTK_STOCK_CLOSE, NULL); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); + g_signal_connect(G_OBJECT(mi), "activate", + (GCallback)menu_close_window, tb); + gtk_widget_show (mi); + + if (tb->menu) + gtk_widget_destroy(tb->menu); + tb->menu = menu; +} + +static void +taskbar_size_alloc(GtkWidget *widget, GtkAllocation *a, + taskbar_priv *tb) +{ + int dim; + + ENTER; + if (tb->plugin.panel->orientation == GTK_ORIENTATION_HORIZONTAL) + dim = a->height / tb->task_height_max; + else + dim = a->width / tb->task_width_max; + DBG("width=%d height=%d task_height_max=%d -> dim=%d\n", + a->width, a->height, tb->task_height_max, dim); + gtk_bar_set_dimension(GTK_BAR(tb->bar), dim); + RET(); +} + +static void +taskbar_build_gui(plugin_instance *p) +{ + taskbar_priv *tb = (taskbar_priv *) p; + GtkWidget *ali; + + ENTER; + if (p->panel->orientation == GTK_ORIENTATION_HORIZONTAL) + ali = gtk_alignment_new(0.0, 0.5, 0, 0); + else + ali = gtk_alignment_new(0.5, 0.0, 0, 0); + g_signal_connect(G_OBJECT(ali), "size-allocate", + (GCallback) taskbar_size_alloc, tb); + gtk_container_set_border_width(GTK_CONTAINER(ali), 0); + gtk_container_add(GTK_CONTAINER(p->pwid), ali); + + tb->bar = gtk_bar_new(p->panel->orientation, tb->spacing, + tb->task_height_max, tb->task_width_max); + gtk_container_set_border_width(GTK_CONTAINER(tb->bar), 0); + gtk_container_add(GTK_CONTAINER(ali), tb->bar); + gtk_widget_show_all(ali); + + tb->gen_pixbuf = gdk_pixbuf_new_from_xpm_data((const char **)icon_xpm); + + gdk_window_add_filter(NULL, (GdkFilterFunc)tb_event_filter, tb ); + + g_signal_connect (G_OBJECT (fbev), "current_desktop", + G_CALLBACK (tb_net_current_desktop), (gpointer) tb); + g_signal_connect (G_OBJECT (fbev), "active_window", + G_CALLBACK (tb_net_active_window), (gpointer) tb); + g_signal_connect (G_OBJECT (fbev), "number_of_desktops", + G_CALLBACK (tb_net_number_of_desktops), (gpointer) tb); + g_signal_connect (G_OBJECT (fbev), "client_list", + G_CALLBACK (tb_net_client_list), (gpointer) tb); + g_signal_connect (G_OBJECT (fbev), "desktop_names", + G_CALLBACK (tb_make_menu), (gpointer) tb); + g_signal_connect (G_OBJECT (fbev), "number_of_desktops", + G_CALLBACK (tb_make_menu), (gpointer) tb); + + tb->desk_num = get_net_number_of_desktops(); + tb->cur_desk = get_net_current_desktop(); + tb->focused = NULL; + tb->menu = NULL; + + tb_make_menu(NULL, tb); + gtk_container_set_border_width(GTK_CONTAINER(p->pwid), 0); + gtk_widget_show_all(tb->bar); + RET(); +} + +void net_active_detect() +{ + int nitens; + Atom *data; + + data = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_SUPPORTED, XA_ATOM, &nitens); + if (!data) + return; + + while (nitens > 0) + if(data[--nitens]==a_NET_ACTIVE_WINDOW) { + use_net_active = TRUE; + break; + } + + XFree(data); +} + +int +taskbar_constructor(plugin_instance *p) +{ + taskbar_priv *tb; + GtkRequisition req; + xconf *xc = p->xc; + + ENTER; + tb = (taskbar_priv *) p; + gtk_rc_parse_string(taskbar_rc); + get_button_spacing(&req, GTK_CONTAINER(p->pwid), ""); + net_active_detect(); + + tb->topxwin = p->panel->topxwin; + tb->tooltips = 1; + tb->icons_only = 0; + tb->accept_skip_pager = 1; + tb->show_iconified = 1; + tb->show_mapped = 1; + tb->show_all_desks = 0; + tb->task_width_max = TASK_WIDTH_MAX; + tb->task_height_max = p->panel->max_elem_height; + tb->task_list = g_hash_table_new(g_int_hash, g_int_equal); + tb->focused_state = GTK_STATE_ACTIVE; + tb->normal_state = GTK_STATE_NORMAL; + tb->spacing = 0; + tb->use_mouse_wheel = 1; + tb->use_urgency_hint = 1; + + XCG(xc, "tooltips", &tb->tooltips, enum, bool_enum); + XCG(xc, "iconsonly", &tb->icons_only, enum, bool_enum); + XCG(xc, "acceptskippager", &tb->accept_skip_pager, enum, bool_enum); + XCG(xc, "showiconified", &tb->show_iconified, enum, bool_enum); + XCG(xc, "showalldesks", &tb->show_all_desks, enum, bool_enum); + XCG(xc, "showmapped", &tb->show_mapped, enum, bool_enum); + XCG(xc, "usemousewheel", &tb->use_mouse_wheel, enum, bool_enum); + XCG(xc, "useurgencyhint", &tb->use_urgency_hint, enum, bool_enum); + XCG(xc, "maxtaskwidth", &tb->task_width_max, int); + + /* FIXME: until per-plugin elem height limit is ready, lets + * use hardcoded TASK_HEIGHT_MAX pixels */ + if (tb->task_height_max > TASK_HEIGHT_MAX) + tb->task_height_max = TASK_HEIGHT_MAX; + if (p->panel->orientation == GTK_ORIENTATION_HORIZONTAL) { + tb->iconsize = MIN(p->panel->ah, tb->task_height_max) - req.height; + if (tb->icons_only) + tb->task_width_max = tb->iconsize + req.width; + } else { + if (p->panel->aw <= 30) + tb->icons_only = 1; + tb->iconsize = MIN(p->panel->aw, tb->task_height_max) - req.height; + if (tb->icons_only) + tb->task_width_max = tb->iconsize + req.height; + } + taskbar_build_gui(p); + tb_net_client_list(NULL, tb); + tb_display(tb); + tb_net_active_window(NULL, tb); + RET(1); +} + + +static void +taskbar_destructor(plugin_instance *p) +{ + taskbar_priv *tb = (taskbar_priv *) p; + + ENTER; + gdk_window_remove_filter(NULL, (GdkFilterFunc)tb_event_filter, tb); + g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), + tb_net_current_desktop, tb); + g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), + tb_net_active_window, tb); + g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), + tb_net_number_of_desktops, tb); + g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), + tb_net_client_list, tb); + + g_hash_table_foreach_remove(tb->task_list, (GHRFunc) task_remove_every, + NULL); + g_hash_table_destroy(tb->task_list); + if (tb->wins) + XFree(tb->wins); + //gtk_widget_destroy(tb->bar); // destroy of p->pwid does it all + gtk_widget_destroy(tb->menu); + DBG("alloc_no=%d\n", tb->alloc_no); + RET(); +} + +static plugin_class class = { + .fname = NULL, + .count = 0, + .type = "taskbar", + .name = "Taskbar", + .version = "1.0", + .description = "Shows opened windows", + .priv_size = sizeof(taskbar_priv), + + .constructor = taskbar_constructor, + .destructor = taskbar_destructor, +}; + +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/tclock/Makefile b/plugins/tclock/Makefile new file mode 100644 index 0000000..47d1a82 --- /dev/null +++ b/plugins/tclock/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +tclock_src = tclock.c +tclock_cflags = -DPLUGIN $(GTK2_CFLAGS) +tclock_libs = $(GTK2_LIBS) +tclock_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/tclock/tclock.c b/plugins/tclock/tclock.c new file mode 100644 index 0000000..d8a9374 --- /dev/null +++ b/plugins/tclock/tclock.c @@ -0,0 +1,191 @@ +#include +#include +#include +#include +#include +#include + + +#include "panel.h" +#include "misc.h" +#include "plugin.h" + +//#define DEBUGPRN +#include "dbg.h" + +/* 2010-04 Jared Minch < jmminch@sourceforge.net > + * Calendar and transparency support + * See patch "2981313: Enhancements to 'tclock' plugin" on sf.net + */ + +#define TOOLTIP_FMT "%A %x" +#define CLOCK_24H_FMT "%R" +#define CLOCK_12H_FMT "%I:%M" + +typedef struct { + plugin_instance plugin; + GtkWidget *main; + GtkWidget *clockw; + GtkWidget *calendar_window; + char *tfmt; + char *cfmt; + char *action; + short lastDay; + int timer; + int show_calendar; + int show_tooltip; +} tclock_priv; + +static GtkWidget * +tclock_create_calendar(void) +{ + GtkWidget *calendar, *win; + + win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size(GTK_WINDOW(win), 180, 180); + gtk_window_set_decorated(GTK_WINDOW(win), FALSE); + gtk_window_set_resizable(GTK_WINDOW(win), FALSE); + gtk_container_set_border_width(GTK_CONTAINER(win), 5); + gtk_window_set_skip_taskbar_hint(GTK_WINDOW(win), TRUE); + gtk_window_set_skip_pager_hint(GTK_WINDOW(win), TRUE); + gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_MOUSE); + gtk_window_set_title(GTK_WINDOW(win), "calendar"); + gtk_window_stick(GTK_WINDOW(win)); + + calendar = gtk_calendar_new(); + gtk_calendar_display_options( + GTK_CALENDAR(calendar), + GTK_CALENDAR_SHOW_WEEK_NUMBERS | GTK_CALENDAR_SHOW_DAY_NAMES + | GTK_CALENDAR_SHOW_HEADING); + gtk_container_add(GTK_CONTAINER(win), GTK_WIDGET(calendar)); + + return win; +} + +static gint +clock_update(gpointer data) +{ + char output[256]; + time_t now; + struct tm * detail; + tclock_priv *dc; + gchar *utf8; + size_t rc; + + ENTER; + g_assert(data != NULL); + dc = (tclock_priv *)data; + + time(&now); + detail = localtime(&now); + rc = strftime(output, sizeof(output), dc->cfmt, detail) ; + if (rc) { + gtk_label_set_markup (GTK_LABEL(dc->clockw), output) ; + } + + if (dc->show_tooltip) { + if (dc->calendar_window) { + gtk_widget_set_tooltip_markup(dc->main, NULL); + dc->lastDay = 0; + } else { + if (detail->tm_mday != dc->lastDay) { + dc->lastDay = detail->tm_mday; + + rc = strftime(output, sizeof(output), dc->tfmt, detail) ; + if (rc && + (utf8 = g_locale_to_utf8(output, -1, NULL, NULL, NULL))) { + gtk_widget_set_tooltip_markup(dc->main, utf8); + g_free(utf8); + } + } + } + } + + RET(TRUE); +} + +static gboolean +clicked(GtkWidget *widget, GdkEventButton *event, tclock_priv *dc) +{ + ENTER; + if (dc->action) { + g_spawn_command_line_async(dc->action, NULL); + } else if (dc->show_calendar) { + if (dc->calendar_window == NULL) + { + dc->calendar_window = tclock_create_calendar(); + gtk_widget_show_all(dc->calendar_window); + } + else + { + gtk_widget_destroy(dc->calendar_window); + dc->calendar_window = NULL; + } + + clock_update(dc); + } + RET(TRUE); +} + +static int +tclock_constructor(plugin_instance *p) +{ + tclock_priv *dc; + + ENTER; + dc = (tclock_priv *) p; + dc->cfmt = CLOCK_24H_FMT; + dc->tfmt = TOOLTIP_FMT; + dc->action = NULL; + dc->show_calendar = TRUE; + dc->show_tooltip = TRUE; + XCG(p->xc, "TooltipFmt", &dc->tfmt, str); + XCG(p->xc, "ClockFmt", &dc->cfmt, str); + XCG(p->xc, "Action", &dc->action, str); + XCG(p->xc, "ShowCalendar", &dc->show_calendar, enum, bool_enum); + XCG(p->xc, "ShowTooltip", &dc->show_tooltip, enum, bool_enum); + + dc->main = gtk_event_box_new(); + gtk_event_box_set_visible_window(GTK_EVENT_BOX(dc->main), FALSE); + if (dc->action || dc->show_calendar) + g_signal_connect (G_OBJECT (dc->main), "button_press_event", + G_CALLBACK (clicked), (gpointer) dc); + + dc->clockw = gtk_label_new(NULL); + + clock_update(dc); + + gtk_misc_set_alignment(GTK_MISC(dc->clockw), 0.5, 0.5); + gtk_misc_set_padding(GTK_MISC(dc->clockw), 4, 0); + gtk_label_set_justify(GTK_LABEL(dc->clockw), GTK_JUSTIFY_CENTER); + gtk_container_add(GTK_CONTAINER(dc->main), dc->clockw); + gtk_widget_show_all(dc->main); + dc->timer = g_timeout_add(1000, (GSourceFunc) clock_update, (gpointer)dc); + gtk_container_add(GTK_CONTAINER(p->pwid), dc->main); + RET(1); +} + +static void +tclock_destructor( plugin_instance *p ) +{ + tclock_priv *dc = (tclock_priv *) p; + + ENTER; + if (dc->timer) + g_source_remove(dc->timer); + gtk_widget_destroy(dc->main); + RET(); +} + +static plugin_class class = { + .count = 0, + .type = "tclock", + .name = "Text Clock", + .version = "2.0", + .description = "Text clock/date with tooltip", + .priv_size = sizeof(tclock_priv), + + .constructor = tclock_constructor, + .destructor = tclock_destructor, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/unstable/Makefile b/plugins/unstable/Makefile new file mode 100644 index 0000000..53a6e32 --- /dev/null +++ b/plugins/unstable/Makefile @@ -0,0 +1,7 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +SUBDIRS := test + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/unstable/test/Makefile b/plugins/unstable/test/Makefile new file mode 100644 index 0000000..890c533 --- /dev/null +++ b/plugins/unstable/test/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../../.. + +test_src = test.c +test_cflags = $(GTK2_CFLAGS) +test_libs = $(GTK2_LIBS) +test_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/unstable/test/test.c b/plugins/unstable/test/test.c new file mode 100644 index 0000000..947dd85 --- /dev/null +++ b/plugins/unstable/test/test.c @@ -0,0 +1,103 @@ + +/* + * test - test module. its purpose to continuously change its size by + * allocating and destroying widgets. It helps in debuging panels's + * geometry engine (panel.c ) + */ + + + +#include +#include +#include +#include +#include +#include + + +#include "panel.h" +#include "misc.h" +#include "plugin.h" + +//#define DEBUGPRN +#include "dbg.h" + + +#define WID_NUM 80 + +typedef struct { + plugin_instance plugin; + GtkWidget *main; + int count; + int delta; + int timer; + GtkWidget *wid[WID_NUM]; +} test_priv; + +//static dclock me; + +static gint +clock_update(gpointer data) +{ + test_priv *dc = (test_priv *)data; + + ENTER; + if (dc->count >= WID_NUM-1) + dc->delta = -1; + else if (dc->count <= 0) + dc->delta = 1; + if (dc->delta == 1) { + dc->wid[dc->count] = gtk_button_new_with_label(" wwwww "); + gtk_widget_show( dc->wid[dc->count] ); + gtk_box_pack_start(GTK_BOX(dc->main), dc->wid[dc->count], TRUE, FALSE, 0); + } else + gtk_widget_destroy(dc->wid[dc->count]); + dc->count += dc->delta; + RET(TRUE); +} + + +static int +test_constructor(plugin_instance *p) +{ + test_priv *dc; + line s; + + ENTER; + dc = (test_priv *) p; + dc->delta = 1; + while (get_line(p->fp, &s) != LINE_BLOCK_END) { + ERR( "test: illegal in this context %s\n", s.str); + } + dc->main = p->panel->my_box_new(TRUE, 1); + gtk_widget_show(dc->main); + gtk_container_add(GTK_CONTAINER(p->pwid), dc->main); + dc->timer = g_timeout_add(200, clock_update, (gpointer)dc); + RET(1); +} + + +static void +test_destructor(plugin_instance *p) +{ + test_priv *dc = (test_priv *) p; + + ENTER; + if (dc->timer) + g_source_remove(dc->timer); + gtk_widget_destroy(dc->main); + RET(); +} + +static plugin_class class = { + .count = 0, + .type = "test", + .name = "Test plugin", + .version = "1.0", + .description = "Creates and destroys widgets", + .priv_size = sizeof(test_priv), + + .constructor = test_constructor, + .destructor = test_destructor, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/volume/Makefile b/plugins/volume/Makefile new file mode 100644 index 0000000..235a9fc --- /dev/null +++ b/plugins/volume/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +volume_src = volume.c +volume_cflags = -DPLUGIN $(GTK2_CFLAGS) +volume_libs = $(GTK2_LIBS) +volume_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/volume/volume.c b/plugins/volume/volume.c new file mode 100644 index 0000000..ffebc60 --- /dev/null +++ b/plugins/volume/volume.c @@ -0,0 +1,305 @@ +/* + * OSS volume plugin. Will works with ALSA since it usually + * emulates OSS layer. + */ + +#include "misc.h" +#include "../meter/meter.h" +#include +#include +#include +#include +#if defined __linux__ +#include +#endif + +//#define DEBUGPRN +#include "dbg.h" + +static gchar *names[] = { + "stock_volume-min", + "stock_volume-med", + "stock_volume-max", + NULL +}; + +static gchar *s_names[] = { + "stock_volume-mute", + NULL +}; + +typedef struct { + meter_priv meter; + int fd, chan; + guchar vol, muted_vol; + int update_id, leave_id; + int has_pointer; + gboolean muted; + GtkWidget *slider_window; + GtkWidget *slider; +} volume_priv; + +static meter_class *k; + +static void slider_changed(GtkRange *range, volume_priv *c); +static gboolean crossed(GtkWidget *widget, GdkEventCrossing *event, + volume_priv *c); + +static int +oss_get_volume(volume_priv *c) +{ + int volume; + + ENTER; + if (ioctl(c->fd, MIXER_READ(c->chan), &volume)) { + ERR("volume: can't get volume from /dev/mixer\n"); + RET(0); + } + volume &= 0xFF; + DBG("volume=%d\n", volume); + RET(volume); +} + +static void +oss_set_volume(volume_priv *c, int volume) +{ + ENTER; + DBG("volume=%d\n", volume); + volume = (volume << 8) | volume; + ioctl(c->fd, MIXER_WRITE(c->chan), &volume); + RET(); +} + +static gboolean +volume_update_gui(volume_priv *c) +{ + int volume; + gchar buf[20]; + + ENTER; + volume = oss_get_volume(c); + if ((volume != 0) != (c->vol != 0)) { + if (volume) + k->set_icons(&c->meter, names); + else + k->set_icons(&c->meter, s_names); + DBG("seting %s icons\n", volume ? "normal" : "muted"); + } + c->vol = volume; + k->set_level(&c->meter, volume); + g_snprintf(buf, sizeof(buf), "Volume: %d%%", volume); + if (!c->slider_window) + gtk_widget_set_tooltip_markup(((plugin_instance *)c)->pwid, buf); + else { + g_signal_handlers_block_by_func(G_OBJECT(c->slider), + G_CALLBACK(slider_changed), c); + gtk_range_set_value(GTK_RANGE(c->slider), volume); + g_signal_handlers_unblock_by_func(G_OBJECT(c->slider), + G_CALLBACK(slider_changed), c); + } + RET(TRUE); +} + +static void +slider_changed(GtkRange *range, volume_priv *c) +{ + int volume = (int) gtk_range_get_value(range); + ENTER; + DBG("value=%d\n", volume); + oss_set_volume(c, volume); + volume_update_gui(c); + RET(); +} + +static GtkWidget * +volume_create_slider(volume_priv *c) +{ + GtkWidget *slider, *win; + GtkWidget *frame; + + win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size(GTK_WINDOW(win), 180, 180); + gtk_window_set_decorated(GTK_WINDOW(win), FALSE); + gtk_window_set_resizable(GTK_WINDOW(win), FALSE); + gtk_container_set_border_width(GTK_CONTAINER(win), 1); + gtk_window_set_skip_taskbar_hint(GTK_WINDOW(win), TRUE); + gtk_window_set_skip_pager_hint(GTK_WINDOW(win), TRUE); + gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_MOUSE); + gtk_window_stick(GTK_WINDOW(win)); + + frame = gtk_frame_new(NULL); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); + gtk_container_add(GTK_CONTAINER(win), frame); + gtk_container_set_border_width(GTK_CONTAINER(frame), 1); + + slider = gtk_vscale_new_with_range(0.0, 100.0, 1.0); + gtk_widget_set_size_request(slider, 25, 82); + gtk_scale_set_draw_value(GTK_SCALE(slider), TRUE); + gtk_scale_set_value_pos(GTK_SCALE(slider), GTK_POS_BOTTOM); + gtk_scale_set_digits(GTK_SCALE(slider), 0); + gtk_range_set_inverted(GTK_RANGE(slider), TRUE); + gtk_range_set_value(GTK_RANGE(slider), ((meter_priv *) c)->level); + DBG("meter->level %f\n", ((meter_priv *) c)->level); + g_signal_connect(G_OBJECT(slider), "value_changed", + G_CALLBACK(slider_changed), c); + g_signal_connect(G_OBJECT(slider), "enter-notify-event", + G_CALLBACK(crossed), (gpointer)c); + g_signal_connect(G_OBJECT(slider), "leave-notify-event", + G_CALLBACK(crossed), (gpointer)c); + gtk_container_add(GTK_CONTAINER(frame), slider); + + c->slider = slider; + return win; +} + +static gboolean +icon_clicked(GtkWidget *widget, GdkEventButton *event, volume_priv *c) +{ + int volume; + + ENTER; + if (event->type == GDK_BUTTON_PRESS && event->button == 1) { + if (c->slider_window == NULL) { + c->slider_window = volume_create_slider(c); + gtk_widget_show_all(c->slider_window); + gtk_widget_set_tooltip_markup(((plugin_instance *)c)->pwid, NULL); + } else { + gtk_widget_destroy(c->slider_window); + c->slider_window = NULL; + if (c->leave_id) { + g_source_remove(c->leave_id); + c->leave_id = 0; + } + } + RET(FALSE); + } + if (!(event->type == GDK_BUTTON_PRESS && event->button == 2)) + RET(FALSE); + + if (c->muted) { + volume = c->muted_vol; + } else { + c->muted_vol = c->vol; + volume = 0; + } + c->muted = !c->muted; + oss_set_volume(c, volume); + volume_update_gui(c); + RET(FALSE); +} + +static gboolean +icon_scrolled(GtkWidget *widget, GdkEventScroll *event, volume_priv *c) +{ + int volume; + + ENTER; + volume = (c->muted) ? c->muted_vol : ((meter_priv *) c)->level; + volume += 2 * ((event->direction == GDK_SCROLL_UP + || event->direction == GDK_SCROLL_LEFT) ? 1 : -1); + if (volume > 100) + volume = 100; + if (volume < 0) + volume = 0; + + if (c->muted) + c->muted_vol = volume; + else { + oss_set_volume(c, volume); + volume_update_gui(c); + } + RET(TRUE); +} + +static gboolean +leave_cb(volume_priv *c) +{ + ENTER; + c->leave_id = 0; + c->has_pointer = 0; + gtk_widget_destroy(c->slider_window); + c->slider_window = NULL; + RET(FALSE); +} + +static gboolean +crossed(GtkWidget *widget, GdkEventCrossing *event, volume_priv *c) +{ + ENTER; + if (event->type == GDK_ENTER_NOTIFY) + c->has_pointer++; + else + c->has_pointer--; + if (c->has_pointer > 0) { + if (c->leave_id) { + g_source_remove(c->leave_id); + c->leave_id = 0; + } + } else { + if (!c->leave_id && c->slider_window) { + c->leave_id = g_timeout_add(1200, (GSourceFunc) leave_cb, c); + } + } + DBG("has_pointer=%d\n", c->has_pointer); + RET(FALSE); +} + +static int +volume_constructor(plugin_instance *p) +{ + volume_priv *c; + + if (!(k = class_get("meter"))) + RET(0); + if (!PLUGIN_CLASS(k)->constructor(p)) + RET(0); + c = (volume_priv *) p; + if ((c->fd = open ("/dev/mixer", O_RDWR, 0)) < 0) { + ERR("volume: can't open /dev/mixer\n"); + RET(0); + } + k->set_icons(&c->meter, names); + c->update_id = g_timeout_add(1000, (GSourceFunc) volume_update_gui, c); + c->vol = 200; + c->chan = SOUND_MIXER_VOLUME; + volume_update_gui(c); + g_signal_connect(G_OBJECT(p->pwid), "scroll-event", + G_CALLBACK(icon_scrolled), (gpointer) c); + g_signal_connect(G_OBJECT(p->pwid), "button_press_event", + G_CALLBACK(icon_clicked), (gpointer)c); + g_signal_connect(G_OBJECT(p->pwid), "enter-notify-event", + G_CALLBACK(crossed), (gpointer)c); + g_signal_connect(G_OBJECT(p->pwid), "leave-notify-event", + G_CALLBACK(crossed), (gpointer)c); + + RET(1); +} + +static void +volume_destructor(plugin_instance *p) +{ + volume_priv *c = (volume_priv *) p; + + ENTER; + g_source_remove(c->update_id); + if (c->slider_window) + gtk_widget_destroy(c->slider_window); + PLUGIN_CLASS(k)->destructor(p); + class_put("meter"); + RET(); +} + + + +static plugin_class class = { + .count = 0, + .type = "volume", + .name = "Volume", + .version = "2.0", + .description = "OSS volume control", + .priv_size = sizeof(volume_priv), + .constructor = volume_constructor, + .destructor = volume_destructor, +}; + +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/plugins/wincmd/Makefile b/plugins/wincmd/Makefile new file mode 100644 index 0000000..da8fc60 --- /dev/null +++ b/plugins/wincmd/Makefile @@ -0,0 +1,10 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := ../.. + +wincmd_src = wincmd.c +wincmd_cflags = -DPLUGIN $(GTK2_CFLAGS) +wincmd_libs = $(GTK2_LIBS) +wincmd_type = lib + +include $(TOPDIR)/.config/rules.mk diff --git a/plugins/wincmd/wincmd.c b/plugins/wincmd/wincmd.c new file mode 100644 index 0000000..8a5e24a --- /dev/null +++ b/plugins/wincmd/wincmd.c @@ -0,0 +1,220 @@ +#include + +#include + +#include "panel.h" +#include "misc.h" +#include "plugin.h" +#include "gtkbgbox.h" +//#define DEBUGPRN +#include "dbg.h" + + +typedef struct { + plugin_instance plugin; + GdkPixmap *pix; + GdkBitmap *mask; + int button1, button2; + int action1, action2; +} wincmd_priv; + +enum { WC_NONE, WC_ICONIFY, WC_SHADE }; + + +xconf_enum wincmd_enum[] = { + { .num = WC_NONE, .str = "none" }, + { .num = WC_ICONIFY, .str = "iconify" }, + { .num = WC_SHADE, .str = "shade" }, + { .num = 0, .str = NULL }, +}; + +static void +toggle_shaded(wincmd_priv *wc, guint32 action) +{ + Window *win = NULL; + int num, i; + guint32 tmp2, dno; + net_wm_window_type nwwt; + + ENTER; + win = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_CLIENT_LIST, + XA_WINDOW, &num); + if (!win) + RET(); + if (!num) + goto end; + //tmp = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, + // XA_CARDINAL, 0); + //dno = *tmp; + dno = get_net_current_desktop(); + DBG("wincmd: #desk=%d\n", dno); + //XFree(tmp); + for (i = 0; i < num; i++) { + int skip; + + tmp2 = get_net_wm_desktop(win[i]); + DBG("wincmd: win=0x%x dno=%d...", win[i], tmp2); + if ((tmp2 != -1) && (tmp2 != dno)) { + DBG("skip - not cur desk\n"); + continue; + } + get_net_wm_window_type(win[i], &nwwt); + skip = (nwwt.dock || nwwt.desktop || nwwt.splash); + if (skip) { + DBG("skip - omnipresent window type\n"); + continue; + } + Xclimsg(win[i], a_NET_WM_STATE, + action ? a_NET_WM_STATE_ADD : a_NET_WM_STATE_REMOVE, + a_NET_WM_STATE_SHADED, 0, 0, 0); + DBG("ok\n"); + } + + end: + XFree(win); + RET(); +} + +/* if all windows are iconified then open all, + * if any are open then iconify 'em */ +static void +toggle_iconify(wincmd_priv *wc) +{ + Window *win, *awin; + int num, i, j, dno, raise; + guint32 tmp; + net_wm_window_type nwwt; + net_wm_state nws; + + ENTER; + win = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_CLIENT_LIST_STACKING, + XA_WINDOW, &num); + if (!win) + RET(); + if (!num) + goto end; + awin = g_new(Window, num); + dno = get_net_current_desktop(); + raise = 1; + for (j = 0, i = 0; i < num; i++) { + tmp = get_net_wm_desktop(win[i]); + DBG("wincmd: win=0x%x dno=%d...", win[i], tmp); + if ((tmp != -1) && (tmp != dno)) + continue; + + get_net_wm_window_type(win[i], &nwwt); + tmp = (nwwt.dock || nwwt.desktop || nwwt.splash); + if (tmp) + continue; + + get_net_wm_state(win[i], &nws); + raise = raise && (nws.hidden || nws.shaded);; + awin[j++] = win[i]; + } + while (j-- > 0) { + if (raise) + XMapWindow (GDK_DISPLAY(), awin[j]); + else + XIconifyWindow(GDK_DISPLAY(), awin[j], DefaultScreen(GDK_DISPLAY())); + } + + g_free(awin); + end: + XFree(win); + RET(); +} + +static gint +clicked (GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + wincmd_priv *wc = (wincmd_priv *) data; + + ENTER; + if (event->type != GDK_BUTTON_PRESS) + RET(FALSE); + + if (event->button == 1) { + toggle_iconify(wc); + } else if (event->button == 2) { + wc->action2 = 1 - wc->action2; + toggle_shaded(wc, wc->action2); + DBG("wincmd: shade all\n"); + } else { + DBG("wincmd: unsupported command\n"); + } + + RET(FALSE); +} + +static void +wincmd_destructor(plugin_instance *p) +{ + wincmd_priv *wc = (wincmd_priv *)p; + + ENTER; + if (wc->mask) + g_object_unref(wc->mask); + if (wc->pix) + g_object_unref(wc->pix); + RET(); +} + +static int +wincmd_constructor(plugin_instance *p) +{ + gchar *tooltip, *fname, *iname; + wincmd_priv *wc; + GtkWidget *button; + int w, h; + + ENTER; + wc = (wincmd_priv *) p; + tooltip = fname = iname = NULL; + wc->button1 = WC_ICONIFY; + wc->button2 = WC_SHADE; + XCG(p->xc, "Button1", &wc->button1, enum, wincmd_enum); + XCG(p->xc, "Button2", &wc->button2, enum, wincmd_enum); + XCG(p->xc, "Icon", &iname, str); + XCG(p->xc, "Image", &fname, str); + XCG(p->xc, "tooltip", &tooltip, str); + fname = expand_tilda(fname); + + if (p->panel->orientation == GTK_ORIENTATION_HORIZONTAL) { + w = -1; + h = p->panel->max_elem_height; + } else { + w = p->panel->max_elem_height; + h = -1; + } + button = fb_button_new(iname, fname, w, h, 0x202020, NULL); + gtk_container_set_border_width(GTK_CONTAINER(button), 0); + g_signal_connect(G_OBJECT(button), "button_press_event", + G_CALLBACK(clicked), (gpointer)wc); + + gtk_widget_show(button); + gtk_container_add(GTK_CONTAINER(p->pwid), button); + if (p->panel->transparent) + gtk_bgbox_set_background(button, BG_INHERIT, + p->panel->tintcolor, p->panel->alpha); + + g_free(fname); + if (tooltip) + gtk_widget_set_tooltip_markup(button, tooltip); + + RET(1); +} + + +static plugin_class class = { + .count = 0, + .type = "wincmd", + .name = "Show desktop", + .version = "1.0", + .description = "Show Desktop button", + .priv_size = sizeof(wincmd_priv), + + + .constructor = wincmd_constructor, + .destructor = wincmd_destructor, +}; +static plugin_class *class_ptr = (plugin_class *) &class; diff --git a/po/Makefile b/po/Makefile new file mode 100644 index 0000000..4284b57 --- /dev/null +++ b/po/Makefile @@ -0,0 +1,5 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := .. + +include $(TOPDIR)/.config/rules.mk diff --git a/po/fr_FR.UTF-8.mo b/po/fr_FR.UTF-8.mo new file mode 100644 index 0000000000000000000000000000000000000000..2ac5199d9dff923ce27d3ab877527e6457ea833d GIT binary patch literal 2513 zcmaKsOKco97{}dGUJGrZVo1LWLf;FDs*z3pl z{q6t$xMjmz0wqFy5cS+TAx6M!>+wSQ=>{Qg1FwRc!Qa64z)6tjFM!*@Du_p%!|PV?e9?Xr8kk=UmdEar6_niiB2j@UuPl0&EB3=i;(?z=j za$X-6?N30S|5?E=K#uzx5TS_gK|JCIyda(U8RY$c1?QppUm(YK5|g<9d2lP(0$JyC zAm{Tg$huwx?**@bc*Ivl|F_^Cw0{9PzD-Du=WPQyjy{m{+5_^uC&2r_VUWkog9t^u z2(tb)kjK3Y@_Mg=9OoM#=ldSW>wQr0Baq|!807I^g1pXmMf)0vNBoHw>%0z=^Eg4? zXB#*S?f~(K7x3c!G|2HSfvn#vAn*Sgh)29$wBIgx0pxryf;+*>AY{%py9?wPcwN?N zAL=etF1vfkz3>pe`79*rqo{jP`R?%j=hBbLF>@SPezkC%Y@dfw`Ob43oFnJUh2vQ% zto2HH1Rpz4`Mmp3BUHWvTps8l*Oou;Kz$6A&u$wkYqGZ7j|S^5`lPK%A3Gu=hO48K zWSbORdw73!l-o1Tw#Wrdex+wY`%I;BqE@3g^m-4csjizQnIH~liMDd5T+j(ww1REm zON`mnI_W6{ZR%c((-}%_E1xlLrLjTA_6#{$ZOf)w)=hnu61k*JVwb#_Na}=F7OSAG z5tFJ(V$!KrL&shmBVBKV0&=NAMqppL`uA`EGC|yuw>n$ zI9aRdn8b`Sl;%^Bc}=GzW>umypLfkF?a^T~lOSGa$a!&!g6to+@nXLW?H1*2A5^@E zL}jYlDvtqG&-43n0)4s0{j$}I zXP@-JI;f*UX*h)ZXF#Yb1QaSwS7ob0;w4eFA=svfkupL_+D0{XEK;fk;d14IZHd4U zr3wp2xCx04ULgIKaKO>=Bw9e85WXU7)mARglLJGgS!!7qqG?~(NpvKu`)JNqWNGH) zTr}n=KdES31**uQ@?beCKNUS*mgS*J`M}=tP`QkO(JY1g2-qblO(|2)RGp$ZQq2mkl%dr=MD9w{$EPQ*r!qKDE{$1eVz4^Sts(<0 zhyAToX>&-%4drlIN9N~_MhCBtW0h*;q6rh*1m{zc2dg@iX41^5G&*Ws(_`mW-t;3w zhh*<_WS5bna&Y&d65N)MO#8c?7;EKz>vn9j+X+t7iow`&PNT2eSv4KIEqDpNGD=rW z2hZhKd9CTN9B#Qkoxx11PzsIQX=64^DRW}H+sWUE#6MmmaZ1_K6(dnr@$vPPzR zIpCw`N8jz>iXO2pkwx&`^Q(@0hO*_tksjVWhK+fjg7S2xV3;OH)$Pa%yRv%)w$!+g zY|Pae`CeE*?RN5jXQ2S8OdQE@qq3lph?pXT$Q~shB;Qh)6dr9O2}mnu(XBAt;V(xU zFO_rFB-#r;_XCD3?@U;O$_@DR}p>tG3 literal 0 HcmV?d00001 diff --git a/po/fr_FR.UTF-8.po b/po/fr_FR.UTF-8.po new file mode 100644 index 0000000..c3eb657 --- /dev/null +++ b/po/fr_FR.UTF-8.po @@ -0,0 +1,216 @@ +# French translations for fbpanel package. +# Copyright (C) 2010 THE fbpanel'S COPYRIGHT HOLDER +# This file is distributed under the same license as the fbpanel package. +# Automatically generated, 2010. +# +msgid "" +msgstr "" +"Project-Id-Version: fbpanel 6.2\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-09-30 02:07+0200\n" +"PO-Revision-Date: 2010-09-30 02:07+0200\n" +"Last-Translator: Automaticaly generated\n" +"Language-Team: none\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: panel/gconf_panel.c:57 +msgid "Visual Effects" +msgstr "Effets visuels" + +#: panel/gconf_panel.c:65 +msgid "Transparency" +msgstr "Transparence" + +#: panel/gconf_panel.c:69 +msgid "Color settings" +msgstr "Couleur" + +#: panel/gconf_panel.c:79 +msgid "Round corners" +msgstr "Coins arrondis" + +#: panel/gconf_panel.c:83 +msgid "Radius is " +msgstr "Rayon de " + +#: panel/gconf_panel.c:87 panel/gconf_panel.c:101 panel/misc.c:89 +msgid "pixels" +msgstr "" + +#: panel/gconf_panel.c:93 +msgid "Autohide" +msgstr "Masquer automatiquement" + +#: panel/gconf_panel.c:97 +msgid "Height when hidden is " +msgstr "Hauteur quand masqué : " + +#: panel/gconf_panel.c:106 +msgid "Max Element Height" +msgstr "Hauteur maxi élément" + +#: panel/gconf_panel.c:138 +msgid "Properties" +msgstr "Propriétés" + +#: panel/gconf_panel.c:146 +msgid "Do not cover by maximized windows" +msgstr "Non recouvert par les fenêtes maximisées" + +#: panel/gconf_panel.c:150 +msgid "Set 'Dock' type" +msgstr "Type 'Dock'" + +#: panel/gconf_panel.c:155 +msgid "Set stacking layer" +msgstr "Couche de recouvrement" + +#: panel/gconf_panel.c:159 +msgid "Panel is " +msgstr "Le panel est " + +#: panel/gconf_panel.c:164 +msgid "all windows" +msgstr "des fenêtres" + +#: panel/gconf_panel.c:209 +msgid "Geometry" +msgstr "Géométrie" + +#: panel/gconf_panel.c:216 +msgid "Width" +msgstr "Largeur" + +#: panel/gconf_panel.c:226 +msgid "Height" +msgstr "Hauteur" + +#: panel/gconf_panel.c:231 +msgid "Edge" +msgstr "Bord écran" + +#: panel/gconf_panel.c:236 +msgid "Allignment" +msgstr "Alignement" + +#: panel/gconf_panel.c:241 +msgid "Margin" +msgstr "Marge" + +#: panel/gconf_panel.c:285 +#, c-format +msgid "" +"You're using '%s' profile, stored at\n" +"%s" +msgstr "" +"Vous utilisez le profil '%s', situé dans\n" +"%s" + +#: panel/gconf_panel.c:388 +msgid "Panel" +msgstr "" + +#: panel/gconf_panel.c:393 +msgid "Plugins" +msgstr "" + +#: panel/gconf_panel.c:398 +msgid "Profile" +msgstr "Profil" +#: panel/misc.c:75 panel/misc.c:81 +msgid "left" +msgstr "gauche" + +#: panel/misc.c:76 panel/misc.c:82 +msgid "right" +msgstr "droite" + +#: panel/misc.c:77 +msgid "center" +msgstr "centre" + +#: panel/misc.c:83 +msgid "top" +msgstr "haut" + +#: panel/misc.c:84 +msgid "bottom" +msgstr "bas" + +#: panel/misc.c:88 +msgid "dynamic" +msgstr "dynamique" + +#: panel/misc.c:90 +#, c-format +msgid "% of screen" +msgstr "% de l'écran" + +#: panel/misc.c:94 +msgid "pixel" +msgstr "" + +#: panel/misc.c:109 +msgid "above" +msgstr "au-dessus" + +#: panel/misc.c:110 +msgid "below" +msgstr "en-dessous" + +#: panel/panel.c:464 +msgid "translator-credits" +msgstr "Calimero " + +#: plugins/menu/system_menu.c:24 +msgid "Audio & Video" +msgstr "Audio & Vidéo" + +#: plugins/menu/system_menu.c:25 +msgid "Education" +msgstr "Éducation" + +#: plugins/menu/system_menu.c:26 +msgid "Game" +msgstr "Jeux" + +#: plugins/menu/system_menu.c:27 +msgid "Graphics" +msgstr "Graphisme" + +#: plugins/menu/system_menu.c:28 +msgid "Network" +msgstr "Réseau" + +#: plugins/menu/system_menu.c:29 +msgid "Office" +msgstr "Bureautique" + +#: plugins/menu/system_menu.c:30 +msgid "Settings" +msgstr "Paramètres" + +#: plugins/menu/system_menu.c:31 +msgid "System" +msgstr "Système" + +#: plugins/menu/system_menu.c:32 +msgid "Utilities" +msgstr "Utilitaires" + +#: plugins/menu/system_menu.c:33 +msgid "Development" +msgstr "Développement" + +#: plugins/taskbar/taskbar.c:1267 +msgid "Raise" +msgstr "Restaurer" + +#: plugins/taskbar/taskbar.c:1274 +msgid "Iconify" +msgstr "Icônifier" + diff --git a/po/ru_RU.UTF-8.mo b/po/ru_RU.UTF-8.mo new file mode 100644 index 0000000000000000000000000000000000000000..bea4f16a30ed11b0a973a65892203ebae3542b89 GIT binary patch literal 3063 zcmaKtTWl0n7{`xzUs1s;-VaKtC{uQe7hGDDOQ|GQ8mJf(HBNWWZbxQkHZx~i){sc4 z7mODWFai-lc`?Ss<+4&Jm*}gBo=JRAqsGJsjW7CQjEOP+|7WLwN}TMO-<;cbzVH8i zGyB~Y=e;3t%|p8pZTmSwECr{YiyN*P=LvBMcpZ2-*al7q?*^|1d%+C&1b8Vp1YQIV zgO`ENg53WaI1}6j;wO&bb}{&I+W!>fJ_B9=eg$3#exLS#0-mvnr}`K@&s5I->; zH`==v-gXq*{0A#-kVoHpFtj~>Ue|yS3Dc=WaubJ`_upRx+LC7vx(yBTh&#|& zqqn2+z2wDOW#0dN&BcI3qjs#P$&0nZJl~8q8_hxEh4QiWYCGwT?=f?53)*#P%&%yZ zVM)e?NU1<{4KC_cVMXbv-ZgJ<5&Qj7SXGhsR6IGd#*1sNFMCQQRnV=`#lG*AgNh2Y zSX?W5p`0z(ctsVWr^6vUiKU?*Mlx1fdqFuC%ha&)!>Zk}ER;d0Wg#3^ksPecio4FM zc+aS!9Pxr;I1-DVVp-uX286#e>Q#4{5QY5C&eUF8bW4>;=F& z+2bo7Ak!5OD?JiMYsJb^$tx()?*__m-K7^x(eKx=F%}58o9^SuFiC6ZKna@4njBK`u-`k z2BGYT7!0)zE201a;EJzGT2#Gth}hm^5n0uASQVOX`7W$GsDh%Wtp-mcxz38Bvj)LJ z^t>z$R;if0r=v5oN>#&1JAHB4D>_SR<=7br^D@)Fa==*{DVrr{nX6S^c4l)~CwreW zKP$7H`E2K%+0JYh3!PPJ*rVb9UH6b1YsboF{mV<%Qw8=5u3wjBg-qa-V&)+?DA(Mw zat4%J$)lVCb!sdxqgtkKMPJY9P;wpF%+e6T1h9_e<)v2Z^t`I?dci_j7;+;NYWE`p z%bf+M*HNXCikzOH5EhZ7yj(Eo=?uyya(!oc7*%4NT(#Y}d;UV%dhEVEXv<~0yK{2( zY{|#$%Hf%h6xd-8nhc)qNKq?T&0$E-TlsZ*9Sv>>TXLVNd6rg&DlG$*bmQ zvcWXWans;?YWCv!usLR$$xEh*(Q(t9d}GZ!=72e2jwGYWMm#l6jc>)eX0pkyJ|ZzS zZVuT=vo(3yT0datu%~5Fn75KKGm*RkL*qQm90D7{Y%vEgbRrq0SJ=zxrZBrrgNMQt zjyFfmxJ+KaF-^>4lXcl1vmaYGVjhFLO;aL}M#~W0#r{{MIRv|7=Ab=xT*BT_xYU3T zg^>Ea>)1V^sCfN)TPupOUCg2QpI!<}a0bzF7M{Afm&@9eRMupj9Rc_MQ ze>xMGW9-N2^&WV2!g_6&Qbwc!Zo){b4F2W$o7UNueiO;-a20l-vlVoLCimIeV3sM= zxRu-{@#N%J4o6HdzEpS#xw#zfG6QGbKSn>PB921_nc@>^Led;1o6k}slpca$HKju) zY`o?$tTmDs>7q>oMA~OvgahywLA-~D4HSu+kV{E3t+GIV4kKC?9?I(^9h(&J%&+ZS znLN*9;P7U=7vuJwVN&HkKDs=?91{OqldMK8X}v%=ThI}fzpVupvw(9f4-}@LG>DB= zm_9bmQ7)jX4Z8$4n^t%@CKd9!WEyfqSBQA9W3V#oO19Ubh Apa1{> literal 0 HcmV?d00001 diff --git a/po/ru_RU.UTF-8.po b/po/ru_RU.UTF-8.po new file mode 100644 index 0000000..d04468e --- /dev/null +++ b/po/ru_RU.UTF-8.po @@ -0,0 +1,218 @@ +# Russian translations for fbpanel package. +# Copyright (C) 2010 THE fbpanel'S COPYRIGHT HOLDER +# This file is distributed under the same license as the fbpanel package. +# Automatically generated, 2010. +# +msgid "" +msgstr "" +"Project-Id-Version: fbpanel 6.2\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-09-30 02:02+0200\n" +"PO-Revision-Date: 2010-09-30 02:02+0200\n" +"Last-Translator: Automaticaly generated\n" +"Language-Team: none\n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: panel/gconf_panel.c:57 +msgid "Visual Effects" +msgstr "Внешний вид" + +#: panel/gconf_panel.c:65 +msgid "Transparency" +msgstr "Прозрачность" + +#: panel/gconf_panel.c:69 +msgid "Color settings" +msgstr "Настройки цвета" + +#: panel/gconf_panel.c:79 +msgid "Round corners" +msgstr "Закруглённые углы" + +#: panel/gconf_panel.c:83 +msgid "Radius is " +msgstr "Радиус" + +#: panel/gconf_panel.c:87 panel/gconf_panel.c:101 panel/misc.c:89 +msgid "pixels" +msgstr "точек" + +#: panel/gconf_panel.c:93 +msgid "Autohide" +msgstr "Автоскрытие" + +#: panel/gconf_panel.c:97 +msgid "Height when hidden is " +msgstr "" + +#: panel/gconf_panel.c:106 +msgid "Max Element Height" +msgstr "Максимальная высота элементов" + +#: panel/gconf_panel.c:138 +msgid "Properties" +msgstr "Свойства" + +#: panel/gconf_panel.c:146 +msgid "Do not cover by maximized windows" +msgstr "Не закрывать другими окнами" + +#: panel/gconf_panel.c:150 +msgid "Set 'Dock' type" +msgstr "Установить тип 'Dock' для окна панели" + +#: panel/gconf_panel.c:155 +msgid "Set stacking layer" +msgstr "Установить уровень отображения" + +#: panel/gconf_panel.c:159 +msgid "Panel is " +msgstr "Панель" + +#: panel/gconf_panel.c:164 +msgid "all windows" +msgstr "другими окнами" + +#: panel/gconf_panel.c:209 +msgid "Geometry" +msgstr "Размещение" + +#: panel/gconf_panel.c:216 +msgid "Width" +msgstr "Ширина" + +#: panel/gconf_panel.c:226 +msgid "Height" +msgstr "Высота" + +#: panel/gconf_panel.c:231 +msgid "Edge" +msgstr "Расположение" + +#: panel/gconf_panel.c:236 +msgid "Allignment" +msgstr "Выравнивание" + +#: panel/gconf_panel.c:241 +msgid "Margin" +msgstr "Отступ" + +#: panel/gconf_panel.c:285 +#, c-format +msgid "" +"You're using '%s' profile, stored at\n" +"%s" +msgstr "" +"Вы используете профиль '%s', храняшийся в \n" +"%s" + +#: panel/gconf_panel.c:388 +msgid "Panel" +msgstr "Панель" + +#: panel/gconf_panel.c:393 +msgid "Plugins" +msgstr "Плагины" + +#: panel/gconf_panel.c:398 +msgid "Profile" +msgstr "Профиль" + +#: panel/misc.c:75 panel/misc.c:81 +msgid "left" +msgstr "слева" + +#: panel/misc.c:76 panel/misc.c:82 +msgid "right" +msgstr "справа" + +#: panel/misc.c:77 +msgid "center" +msgstr "по центру" + +#: panel/misc.c:83 +msgid "top" +msgstr "вверху" + +#: panel/misc.c:84 +msgid "bottom" +msgstr "внизу" + +#: panel/misc.c:88 +msgid "dynamic" +msgstr "" + +#: panel/misc.c:90 +#, c-format +msgid "% of screen" +msgstr "% экрана" + +#: panel/misc.c:94 +msgid "pixel" +msgstr "точка" + +#: panel/misc.c:109 +msgid "above" +msgstr "над" + +#: panel/misc.c:110 +msgid "below" +msgstr "под" + +#: panel/panel.c:464 +msgid "translator-credits" +msgstr "" +"Anatoly Asviyan " + +#: plugins/menu/system_menu.c:24 +msgid "Audio & Video" +msgstr "Мультимедиа" + +#: plugins/menu/system_menu.c:25 +msgid "Education" +msgstr "Образование" + +#: plugins/menu/system_menu.c:26 +msgid "Game" +msgstr "Игры" + +#: plugins/menu/system_menu.c:27 +msgid "Graphics" +msgstr "Графика" + +#: plugins/menu/system_menu.c:28 +msgid "Network" +msgstr "Интернет" + +#: plugins/menu/system_menu.c:29 +msgid "Office" +msgstr "Офис" + +#: plugins/menu/system_menu.c:30 +msgid "Settings" +msgstr "Настройки" + +#: plugins/menu/system_menu.c:31 +msgid "System" +msgstr "Система" + +#: plugins/menu/system_menu.c:32 +msgid "Utilities" +msgstr "Утилиты" + +#: plugins/menu/system_menu.c:33 +msgid "Development" +msgstr "Разработка" + +#: plugins/taskbar/taskbar.c:1267 +msgid "Raise" +msgstr "Развернуть" + +#: plugins/taskbar/taskbar.c:1274 +msgid "Iconify" +msgstr "Свернуть" diff --git a/scripts/Makefile b/scripts/Makefile new file mode 100644 index 0000000..4284b57 --- /dev/null +++ b/scripts/Makefile @@ -0,0 +1,5 @@ +## miniconf makefiles ## 1.1 ## + +TOPDIR := .. + +include $(TOPDIR)/.config/rules.mk diff --git a/scripts/create_po.sh b/scripts/create_po.sh new file mode 100755 index 0000000..55db0b0 --- /dev/null +++ b/scripts/create_po.sh @@ -0,0 +1,63 @@ +#/bin/bash + +function info() +{ + cat < $list + +# Create POT file and set charset to be UTF-8 +2>/dev/null xgettext --package-name=$package --package-version=$version \ + --default-domain=$package --from-code=UTF-8 \ + --force-po -k_ -kc_ -f $list -o - |\ +sed '/^"Content-Type:/ s/CHARSET/UTF-8/' > $pot + +# create language translation files, POs +if [ -n "$update" ]; then + old=/tmp/old-$$ + cp $po $old +fi +tr="${translator:-Automaticaly generated}" +2>/dev/null msginit --no-translator --locale=$locale --input=$pot -o - |\ +sed "/\"Last-Translator:/ s/Auto.*\\\n/$tr\\\n/" > $po +if [ -n "$update" ]; then + scripts/update_po.sh --old=$old --new=$po + rm $old +fi +echo Created $po +rm $pot \ No newline at end of file diff --git a/scripts/custom.sh b/scripts/custom.sh new file mode 100644 index 0000000..e6995eb --- /dev/null +++ b/scripts/custom.sh @@ -0,0 +1,30 @@ + + +######################### +## Custom Settings ## +######################### + +# Note 1: PWD will be that of configure, not scripts directory, so to run script from +# this directory refer it as 'scripts/name' + +# Note 2: values will be evaluated in the same order they were added, so +# if you want libdir's default value to be '$eprefix/lib', add it after prefix + +add_var package "package name, e.g fbpanel or mplayer" fbpanel +add_var version "package version, e.g 6.2 or 1.4.5-alpha2" 6.2 + +# Custom +add_feature dependency "disable dependency tracking" disabled + +add_var glib_cflags "glib cflags" '`RFS=$rfs scripts/rfs-pkg-config --cflags glib-2.0`' +add_var gtk_cflags "gtk cflags" '`RFS=$rfs scripts/rfs-pkg-config --cflags gtk+-2.0`' + +add_var glib_libs "glib libs" '`RFS=$rfs scripts/rfs-pkg-config --libs glib-2.0`' +add_var gtk_libs "gtk libs" '`RFS=$rfs scripts/rfs-pkg-config --libs gtk+-2.0`' + +add_var cflagsx "C flags" '-I$topdir/panel $glib_cflags $gtk_cflags -fPIC' +add_var ldflagsx "linker flags" '$glib_libs $gtk_libs' + +add_var gmodule_libs "gmodule libs" '`scripts/rfs-pkg-config --libs gmodule-2.0`' + +add_feature static_build "build all pluginis into main binary" disabled diff --git a/scripts/endianess.sh b/scripts/endianess.sh new file mode 100755 index 0000000..83bd5ac --- /dev/null +++ b/scripts/endianess.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# When cross compiling, you may put cross compiler directory +# in PATH before native gcc (aka spoofing), or you may set CC +# to exact name of cross compiler: +# CC=/opt/ppc_gcc/bin/gcc endianess + +# x86 and friends are considerd LITTLE endian, all others are BIG +a=`${CC:-gcc} -v 2>&1 | grep Target` +[ $? -ne 0 ] && exit 1 +#echo $a + +if [ "${a/86/}" != "$a" ]; then + echo LITTLE +else + echo BIG +fi + diff --git a/scripts/funcs.sh b/scripts/funcs.sh new file mode 100644 index 0000000..723e7a5 --- /dev/null +++ b/scripts/funcs.sh @@ -0,0 +1,40 @@ + +function error () +{ + echo "$@" + exit 1 +} + +function print_var () +{ + eval echo \"$1=\$$1\" +} + +function check_var () +{ + eval [ -n \"\$$1\" ] && return + [ -z "$2" ] && error "Var $1 was not set" + eval $1="$2" +} + +function parse_argv () +{ + while [ $# -gt 0 ]; do + if [ "$1" == "--help" ]; then + info + help + exit 0 + fi + if [ "${1:0:2}" != "--" ]; then + error "$1 - not a parameter" + fi + tmp="${1:2}" + var=${tmp%%=*} + val=${tmp#*=} + if [ "$var" == "$val" ]; then + let $var=x + fi + eval "$var=\"$val\"" + shift + done +} \ No newline at end of file diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..f842b92 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +usage () +{ + echo "install.sh

" +} + + +[ $# -eq 3 ] && exit 0 +[ $# -lt 4 ] && usage && exit 1 + +idir=${DESTDIR}$2 +p1=$1 +p2=$3 +shift 3 + +echo install -m $p1 -d "$idir" +install -m $p1 -d "$idir" +echo install -m $p2 "$@" "$idir" +install -m $p2 "$@" "$idir" + diff --git a/scripts/install_locale.sh b/scripts/install_locale.sh new file mode 100755 index 0000000..92b88fb --- /dev/null +++ b/scripts/install_locale.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +dir=/tmp/lo-$$ +mkdir $dir +cp $1.mo $dir +cd $dir +mv $1.mo fbpanel.mo + +# Create name for destination dir. +# Locale has format LL_CC.ENC. (LL - language, CC - country, ENC - encoding) +# Name is LL_CC. but if CC is equal to LL, then name is only LL. +IFS="_" a=(${1%%.*}) +lang=${a[0]} +country=${a[1]} +if [ "${lang^^}" == "$country" ]; then + LC="${lang}" +else + LC="${lang}_${country}" +fi + +install=/home/aanatoly/src/fbpanel/trunk/scripts/install.sh +${install} 755 /home/aanatoly/.local/share/locale/$LC/LC_MESSAGES 644 fbpanel.mo +rm -rf $dir + diff --git a/scripts/install_locale.sh.in b/scripts/install_locale.sh.in new file mode 100755 index 0000000..15c2154 --- /dev/null +++ b/scripts/install_locale.sh.in @@ -0,0 +1,24 @@ +#!/bin/bash + +dir=/tmp/lo-$$ +mkdir $dir +cp $1.mo $dir +cd $dir +mv $1.mo %%package%%.mo + +# Create name for destination dir. +# Locale has format LL_CC.ENC. (LL - language, CC - country, ENC - encoding) +# Name is LL_CC. but if CC is equal to LL, then name is only LL. +IFS="_" a=(${1%%.*}) +lang=${a[0]} +country=${a[1]} +if [ "${lang^^}" == "$country" ]; then + LC="${lang}" +else + LC="${lang}_${country}" +fi + +install=%%topdir%%/scripts/install.sh +${install} 755 %%localedir%%/$LC/LC_MESSAGES 644 %%package%%.mo +rm -rf $dir + diff --git a/scripts/mk_tar b/scripts/mk_tar new file mode 100755 index 0000000..3d2f9ac --- /dev/null +++ b/scripts/mk_tar @@ -0,0 +1,53 @@ +#!/bin/bash + + +function info() +{ + cat <" + echo " tar - tar file with latest proj version" + echo "Must be run from top directory" + exit $1 +} + +[ $# -ne 1 ] && usage 1 + +tar="$1" +tar --strip-components=1 --exclude doc --exclude src \ + --keep-newer-files -xvf "$tar" + diff --git a/scripts/update_po.sh b/scripts/update_po.sh new file mode 100755 index 0000000..a077b8e --- /dev/null +++ b/scripts/update_po.sh @@ -0,0 +1,59 @@ +#/bin/bash + +function info() +{ + cat < $tmp +mv $tmp $new + +# code diff --git a/version b/version new file mode 100644 index 0000000..0cda48a --- /dev/null +++ b/version @@ -0,0 +1 @@ +6.2